agora inbox for [email protected]
help / color / mirror / Atom feedA modest proposal: let's add PID to assertion failure messages
268+ messages / 5 participants
[nested] [flat]
* A modest proposal: let's add PID to assertion failure messages
@ 2020-10-04 21:08 Tom Lane <[email protected]>
2020-10-04 21:20 ` Re: A modest proposal: let's add PID to assertion failure messages Thomas Munro <[email protected]>
0 siblings, 1 reply; 268+ messages in thread
From: Tom Lane @ 2020-10-04 21:08 UTC (permalink / raw)
To: [email protected]
In these days when we run almost all test cases in parallel, it's
frequently not that easy to tie a "TRAP: ..." message in the log
to nearby log messages. (The postmaster's subsequent complaint
often helps, but it could be some distance away in the log; and
good luck untangling things if more than one Assert failure happens
concurrently.) We could add a simple bread crumb trail by
including the process's PID in such messages. Any objections?
regards, tom lane
^ permalink raw reply [nested|flat] 268+ messages in thread
* Re: A modest proposal: let's add PID to assertion failure messages
2020-10-04 21:08 A modest proposal: let's add PID to assertion failure messages Tom Lane <[email protected]>
@ 2020-10-04 21:20 ` Thomas Munro <[email protected]>
2020-10-05 00:58 ` Re: A modest proposal: let's add PID to assertion failure messages Michael Paquier <[email protected]>
2020-10-06 18:27 ` Re: A modest proposal: let's add PID to assertion failure messages Andres Freund <[email protected]>
0 siblings, 2 replies; 268+ messages in thread
From: Thomas Munro @ 2020-10-04 21:20 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>
On Mon, Oct 5, 2020 at 10:08 AM Tom Lane <[email protected]> wrote:
> In these days when we run almost all test cases in parallel, it's
> frequently not that easy to tie a "TRAP: ..." message in the log
> to nearby log messages. (The postmaster's subsequent complaint
> often helps, but it could be some distance away in the log; and
> good luck untangling things if more than one Assert failure happens
> concurrently.) We could add a simple bread crumb trail by
> including the process's PID in such messages. Any objections?
+1
^ permalink raw reply [nested|flat] 268+ messages in thread
* Re: A modest proposal: let's add PID to assertion failure messages
2020-10-04 21:08 A modest proposal: let's add PID to assertion failure messages Tom Lane <[email protected]>
2020-10-04 21:20 ` Re: A modest proposal: let's add PID to assertion failure messages Thomas Munro <[email protected]>
@ 2020-10-05 00:58 ` Michael Paquier <[email protected]>
2020-10-05 01:18 ` Re: A modest proposal: let's add PID to assertion failure messages Tom Lane <[email protected]>
1 sibling, 1 reply; 268+ messages in thread
From: Michael Paquier @ 2020-10-05 00:58 UTC (permalink / raw)
To: Thomas Munro <[email protected]>; +Cc: Tom Lane <[email protected]>; PostgreSQL Hackers <[email protected]>
On Mon, Oct 05, 2020 at 10:20:01AM +1300, Thomas Munro wrote:
> On Mon, Oct 5, 2020 at 10:08 AM Tom Lane <[email protected]> wrote:
>> In these days when we run almost all test cases in parallel, it's
>> frequently not that easy to tie a "TRAP: ..." message in the log
>> to nearby log messages. (The postmaster's subsequent complaint
>> often helps, but it could be some distance away in the log; and
>> good luck untangling things if more than one Assert failure happens
>> concurrently.) We could add a simple bread crumb trail by
>> including the process's PID in such messages. Any objections?
>
> +1
+1. (log_line_prefix includes %p in its default configuration for the
TAP tests).
--
Michael
Attachments:
[application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 268+ messages in thread
* Re: A modest proposal: let's add PID to assertion failure messages
2020-10-04 21:08 A modest proposal: let's add PID to assertion failure messages Tom Lane <[email protected]>
2020-10-04 21:20 ` Re: A modest proposal: let's add PID to assertion failure messages Thomas Munro <[email protected]>
2020-10-05 00:58 ` Re: A modest proposal: let's add PID to assertion failure messages Michael Paquier <[email protected]>
@ 2020-10-05 01:18 ` Tom Lane <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Tom Lane @ 2020-10-05 01:18 UTC (permalink / raw)
To: Michael Paquier <[email protected]>; +Cc: Thomas Munro <[email protected]>; PostgreSQL Hackers <[email protected]>
Michael Paquier <[email protected]> writes:
> +1. (log_line_prefix includes %p in its default configuration for the
> TAP tests).
Right, but of course you don't get log_line_prefix on Assert messages.
regards, tom lane
^ permalink raw reply [nested|flat] 268+ messages in thread
* Re: A modest proposal: let's add PID to assertion failure messages
2020-10-04 21:08 A modest proposal: let's add PID to assertion failure messages Tom Lane <[email protected]>
2020-10-04 21:20 ` Re: A modest proposal: let's add PID to assertion failure messages Thomas Munro <[email protected]>
@ 2020-10-06 18:27 ` Andres Freund <[email protected]>
1 sibling, 0 replies; 268+ messages in thread
From: Andres Freund @ 2020-10-06 18:27 UTC (permalink / raw)
To: Thomas Munro <[email protected]>; +Cc: Tom Lane <[email protected]>; PostgreSQL Hackers <[email protected]>
On 2020-10-05 10:20:01 +1300, Thomas Munro wrote:
> On Mon, Oct 5, 2020 at 10:08 AM Tom Lane <[email protected]> wrote:
> > In these days when we run almost all test cases in parallel, it's
> > frequently not that easy to tie a "TRAP: ..." message in the log
> > to nearby log messages. (The postmaster's subsequent complaint
> > often helps, but it could be some distance away in the log; and
> > good luck untangling things if more than one Assert failure happens
> > concurrently.) We could add a simple bread crumb trail by
> > including the process's PID in such messages. Any objections?
>
> +1
+1
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <[email protected]>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
end of thread, other threads:[~2025-10-20 10:29 UTC | newest]
Thread overview: 268+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2020-10-04 21:08 A modest proposal: let's add PID to assertion failure messages Tom Lane <[email protected]>
2020-10-04 21:20 ` Thomas Munro <[email protected]>
2020-10-05 00:58 ` Michael Paquier <[email protected]>
2020-10-05 01:18 ` Tom Lane <[email protected]>
2020-10-06 18:27 ` Andres Freund <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean 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