agora inbox for [email protected]  
help / color / mirror / Atom feed
RE: Foreign key checks/referential integrity.
264+ messages / 2 participants
[nested] [flat]

* RE: Foreign key checks/referential integrity.
@ 2001-04-17 10:34  Michael Ansley <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Michael Ansley @ 2001-04-17 10:34 UTC (permalink / raw)
  To: 'Gavin Sherry' <[email protected]>; pgsql-hackers

No, they shouldn't.  If you want to delete only those tuples that aren't
referenced in b then you must explicitly say so:

delete from a where not exists (select * from b where b.b = a.a);

The query that you tried will explicitly delete all rows from a, thus
violating the constraint on b.  If even one row fails, then the transaction
fails, rolling back any other deletes that may have been successful.

Cheers...


MikeA



>> -----Original Message-----
>> From: Gavin Sherry [mailto:[email protected]]
>> Sent: 17 April 2001 09:59
>> To: [email protected]
>> Subject: [HACKERS] Foreign key checks/referential integrity.
>> 
>> 
>> Hi guys,
>> 
>> I've just come up with a hypothetical which, in my opinion, 
>> points to a
>> flaw in the foreign key implementation in Postgres. All tests were
>> conducted on 7.1beta4 -- not the most up to date, but I have seen no
>> reference to this in the mailing list/todo (ie, in 'foreign' under
>> TODO.detail).
>> 
>> See as follows:
>> 
>> test=# create table a (a int, primary key(a));
>> NOTICE:  CREATE TABLE/PRIMARY KEY will create implicit index 
>> 'a_pkey' for
>> table
>> 'a'
>> CREATE
>> test=# create table b (b int references a(a) match full, 
>> primary key(b));
>> NOTICE:  CREATE TABLE/PRIMARY KEY will create implicit index 
>> 'b_pkey' for
>> table
>> 'b'
>> NOTICE:  CREATE TABLE will create implicit trigger(s) for FOREIGN KEY
>> check(s)
>> CREATE
>> test=# insert into a values(1);
>> INSERT 1754732 1
>> test=# insert into a values(2);
>> INSERT 1754733 1
>> test=# insert into a values(3);
>> INSERT 1754734 1
>> test=# insert into b values(1);
>> INSERT 1754735 1
>> test=# insert into b values(2);
>> INSERT 1754736 1
>> test=# delete from a;
>> ERROR:  <unnamed> referential integrity violation - key in a still
>> referenced from b
>> test=# select * from a;
>>  a
>> ---
>>  1
>>  2
>>  3
>> 
>> 
>> ----
>> 
>> Now, table a has more tuples than b. In my opinion, the 
>> integrity test
>> relates only to those records in a which are in b (since it 
>> is a foreign
>> key reference). Isn't then the query valid for those tuples 
>> which do not
>> result in a violation of the referential integrity test? 
>> Shouldn't those
>> tuples in a be deleted?
>> 
>> Gavin
>> 
>> 
>> 
>> ---------------------------(end of 
>> broadcast)---------------------------
>> TIP 2: you can get off all lists at once with the unregister command
>>     (send "unregister YourEmailAddressHere" to 
>> [email protected])
>> 


_________________________________________________________________________
This e-mail and any attachments are confidential and may also be privileged and/or copyright 
material of Intec Telecom Systems PLC (or its affiliated companies).  If you are not an 
intended or authorised recipient of this e-mail or have received it in error, please delete 
it immediately and notify the sender by e-mail.  In such a case, reading, reproducing, 
printing or further dissemination of this e-mail is strictly prohibited and may be unlawful. 
Intec Telecom Systems PLC. does not represent or warrant that an attachment hereto is free 
from computer viruses or other defects. The opinions expressed in this e-mail and any 
attachments may be those of the author and are not necessarily those of Intec Telecom 
Systems PLC. 

This footnote also confirms that this email message has been swept by
MIMEsweeper for the presence of computer viruses. 
__________________________________________________________________________


^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)

The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious.  Teach psql to detect these two values and print
whatever it deems is appropriate.  In the interest of backward
compatability, that defaults to 't' and 'f'.  However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.

Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
 doc/src/sgml/ref/psql-ref.sgml     | 24 +++++++++++++++++
 src/bin/psql/command.c             | 43 +++++++++++++++++++++++++++++-
 src/fe_utils/print.c               |  4 +++
 src/include/fe_utils/print.h       |  2 ++
 src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
 src/test/regress/sql/psql.sql      | 16 +++++++++++
 6 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
           </listitem>
           </varlistentry>
 
+          <varlistentry id="app-psql-meta-command-pset-display-false">
+          <term><literal>display_false</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a false value.
+          The default is to print <literal>f</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_false 'false'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry id="app-psql-meta-command-pset-display-true">
+          <term><literal>display_true</literal></term>
+          <listitem>
+          <para>
+          Sets the string to be printed in place of a true value.
+          The default is to print <literal>t</literal>, as that is the value
+          transmitted by the server.  For readability,
+          <literal>\pset display_true 'true'</literal> is recommended.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry id="app-psql-meta-command-pset-expanded">
           <term><literal>expanded</literal> (or <literal>x</literal>)</term>
           <listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
 
 			int			i;
 			static const char *const my_list[] = {
-				"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+				"border", "columns", "csv_fieldsep",
+				"display_false", "display_true", "expanded", "fieldsep",
 				"fieldsep_zero", "footer", "format", "linestyle", "null",
 				"numericlocale", "pager", "pager_min_lines",
 				"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 		}
 	}
 
+	/* 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		if (value)
+		{
+			free(popt->falsePrint);
+			popt->falsePrint = pg_strdup(value);
+		}
+	}
+
+	/* 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		if (value)
+		{
+			free(popt->truePrint);
+			popt->truePrint = pg_strdup(value);
+		}
+	}
+
 	/* field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
 			   popt->topt.csvFieldSep);
 	}
 
+	/* show boolean 'false' display */
+	else if (strcmp(param, "display_false") == 0)
+	{
+		printf(_("Boolean false display is \"%s\".\n"),
+			   popt->falsePrint ? popt->falsePrint : "f");
+	}
+
+	/* show boolean 'true' display */
+	else if (strcmp(param, "display_true") == 0)
+	{
+		printf(_("Boolean true display is \"%s\".\n"),
+			   popt->truePrint ? popt->truePrint : "t");
+	}
+
 	/* show field separator for unaligned text */
 	else if (strcmp(param, "fieldsep") == 0)
 	{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
 		return psprintf("%d", popt->topt.columns);
 	else if (strcmp(param, "csv_fieldsep") == 0)
 		return pset_quoted_string(popt->topt.csvFieldSep);
+	else if (strcmp(param, "display_false") == 0)
+		return pset_quoted_string(popt->falsePrint ?
+								  popt->falsePrint : "f");
+	else if (strcmp(param, "display_true") == 0)
+		return pset_quoted_string(popt->truePrint ?
+								  popt->truePrint : "t");
 	else if (strcmp(param, "expanded") == 0)
 		return pstrdup(popt->topt.expanded == 2
 					   ? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 			if (PQgetisnull(result, r, c))
 				cell = opt->nullPrint ? opt->nullPrint : "";
+			else if (PQftype(result, c) == BOOLOID)
+				cell = (PQgetvalue(result, r, c)[0] == 't' ?
+						(opt->truePrint ? opt->truePrint : "t") :
+						(opt->falsePrint ? opt->falsePrint : "f"));
 			else
 			{
 				cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
 	char	   *nullPrint;		/* how to print null entities */
+	char	   *truePrint;		/* how to print boolean true values */
+	char	   *falsePrint;		/* how to print boolean false values */
 	char	   *title;			/* override title */
 	char	  **footers;		/* override footer (default is "(xx rows)") */
 	bool		translate_header;	/* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
 border                   1
 columns                  0
 csv_fieldsep             ','
+display_false            'f'
+display_true             't'
 expanded                 off
 fieldsep                 '|'
 fieldsep_zero            off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
 unicode_column_linestyle single
 unicode_header_linestyle single
 xheader_width            full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+   n    |  t   |   f   
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f 
+---+---+---
+   | t | f
+(1 row)
+
+deallocate q;
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
 -- show all pset options
 \pset
 
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
 -- test multi-line headers, wrapping, and newline indicators
 -- in aligned, unaligned, and wrapped formats
 prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
-- 
2.47.3


--db7q7wsxflbz42ld--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread


end of thread, other threads:[~2025-10-20 10:29 UTC | newest]

Thread overview: 264+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2001-04-17 10:34 RE: Foreign key checks/referential integrity. Michael Ansley <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox