agora inbox for pgsql-hackers@postgresql.org
help / color / mirror / Atom feedRe: [HACKERS] PATCH: multivariate histograms and MCV lists
268+ messages / 4 participants
[nested] [flat]
* Re: [HACKERS] PATCH: multivariate histograms and MCV lists
@ 2018-04-01 16:07 Tomas Vondra <tomas.vondra@2ndquadrant.com>
0 siblings, 1 reply; 268+ messages in thread
From: Tomas Vondra @ 2018-04-01 16:07 UTC (permalink / raw)
To: Dean Rasheed <dean.a.rasheed@gmail.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; Andres Freund <andres@anarazel.de>; Thomas Munro <thomas.munro@enterprisedb.com>; Mark Dilger <hornschnorter@gmail.com>; Adrien Nayrat <adrien.nayrat@dalibo.com>; pgsql-hackers
Hi,
The attached patch version modifies how the non-MCV selectivity is
computed, along the lines explained in the previous message.
The comments in statext_clauselist_selectivity() explain it in far more
detail, but we this:
1) Compute selectivity using the MCV (s1).
2) To compute the non-MCV selectivity (s2) we do this:
2a) See how many top-level equalities are there (and compute ndistinct
estimate for those attributes).
2b) If there is an equality on each column, we know there can only be a
single matching item. If we found it in the MCV (i.e. s1 > 0) we're
done, and 's1' is the answer.
2c) If only some columns have equalities, we estimate the selectivity
for equalities as
s2 = ((1 - mcv_total_sel) / ndistinct)
If there are no remaining conditions, we're done.
2d) To estimate the non-equality clauses (on non-MCV part only), we
either repeat the whole process by calling clauselist_selectivity() or
approximating s1 to the non-MCV part. This needs a bit of care to
prevent infinite loops.
Of course, with 0002 this changes slightly, because we may try using a
histogram to estimate the non-MCV part. But that's just an extra step
right before (2a).
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
[application/gzip] 0001-multivariate-MCV-lists-20180401.patch.gz (38.4K, ../../abfb7b66-af22-ac32-a74d-3fcf9bdb5c5c@2ndquadrant.com/2-0001-multivariate-MCV-lists-20180401.patch.gz)
download
[application/gzip] 0002-multivariate-histograms-20180401.patch.gz (43.0K, ../../abfb7b66-af22-ac32-a74d-3fcf9bdb5c5c@2ndquadrant.com/3-0002-multivariate-histograms-20180401.patch.gz)
download
^ permalink raw reply [nested|flat] 268+ messages in thread
* Re: [HACKERS] PATCH: multivariate histograms and MCV lists
@ 2018-04-07 14:12 Bruce Momjian <bruce@momjian.us>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
0 siblings, 1 reply; 268+ messages in thread
From: Bruce Momjian @ 2018-04-07 14:12 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; +Cc: Dean Rasheed <dean.a.rasheed@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Andres Freund <andres@anarazel.de>; Thomas Munro <thomas.munro@enterprisedb.com>; Mark Dilger <hornschnorter@gmail.com>; Adrien Nayrat <adrien.nayrat@dalibo.com>; pgsql-hackers
On Sun, Apr 1, 2018 at 06:07:55PM +0200, Tomas Vondra wrote:
> Hi,
>
> The attached patch version modifies how the non-MCV selectivity is
> computed, along the lines explained in the previous message.
>
> The comments in statext_clauselist_selectivity() explain it in far more
> detail, but we this:
Uh, where are we on this patch? It isn't going to make it into PG 11?
Feature development for this has been going on for years. I thought
when Dean Rasheed got involved that it would be applied for this
release.
I realize this is a complex feature, but I think it will solve 80-90% of
optimizer complaints, and I don't see any other way to fix them than
this. This seems like the right way to fix optimizer problems, instead
of optimizer hints.
--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com
+ As you are, so once was I. As I am, so you will be. +
+ Ancient Roman grave inscription +
^ permalink raw reply [nested|flat] 268+ messages in thread
* Re: [HACKERS] PATCH: multivariate histograms and MCV lists
@ 2018-04-07 17:52 Dean Rasheed <dean.a.rasheed@gmail.com>
parent: Bruce Momjian <bruce@momjian.us>
0 siblings, 2 replies; 268+ messages in thread
From: Dean Rasheed @ 2018-04-07 17:52 UTC (permalink / raw)
To: Bruce Momjian <bruce@momjian.us>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Andres Freund <andres@anarazel.de>; Thomas Munro <thomas.munro@enterprisedb.com>; Mark Dilger <hornschnorter@gmail.com>; Adrien Nayrat <adrien.nayrat@dalibo.com>; pgsql-hackers
On 7 April 2018 at 15:12, Bruce Momjian <bruce@momjian.us> wrote:
> Uh, where are we on this patch? It isn't going to make it into PG 11?
> Feature development for this has been going on for years.
Unfortunately, I think there's no way that this will be ready for
PG11. So far, I have only read parts of the patch, focusing mainly on
the planner changes, and how it will make use of the new statistics. I
think there are still issues to work out in that area, although I
haven't read the latest version yet, I have some doubts about the new
approach described.
Looking back at the history of this patch, it appears to have moved
through all 4 PG11 commitfests with fairly minimal review, never
becoming Ready for Committer. That's a real shame because I agree that
it's an important feature, but IMO it's not yet in a committable
state. I feel bad saying that, because the patch hasn't really had a
fair chance to-date, despite Tomas' efforts and quick responses to all
review comments.
At this stage though, all I can say is that I'll make every effort to
keep reviewing it, and I hope Tomas will persist, so that it has a
better chance in PG12.
Regards,
Dean
^ permalink raw reply [nested|flat] 268+ messages in thread
* Re: [HACKERS] PATCH: multivariate histograms and MCV lists
@ 2018-04-07 18:34 Bruce Momjian <bruce@momjian.us>
parent: Dean Rasheed <dean.a.rasheed@gmail.com>
1 sibling, 0 replies; 268+ messages in thread
From: Bruce Momjian @ 2018-04-07 18:34 UTC (permalink / raw)
To: Dean Rasheed <dean.a.rasheed@gmail.com>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Andres Freund <andres@anarazel.de>; Thomas Munro <thomas.munro@enterprisedb.com>; Mark Dilger <hornschnorter@gmail.com>; Adrien Nayrat <adrien.nayrat@dalibo.com>; pgsql-hackers
On Sat, Apr 7, 2018 at 06:52:42PM +0100, Dean Rasheed wrote:
> On 7 April 2018 at 15:12, Bruce Momjian <bruce@momjian.us> wrote:
> > Uh, where are we on this patch? It isn't going to make it into PG 11?
> > Feature development for this has been going on for years.
>
> Unfortunately, I think there's no way that this will be ready for
> PG11. So far, I have only read parts of the patch, focusing mainly on
> the planner changes, and how it will make use of the new statistics. I
> think there are still issues to work out in that area, although I
> haven't read the latest version yet, I have some doubts about the new
> approach described.
>
> Looking back at the history of this patch, it appears to have moved
> through all 4 PG11 commitfests with fairly minimal review, never
> becoming Ready for Committer. That's a real shame because I agree that
> it's an important feature, but IMO it's not yet in a committable
> state. I feel bad saying that, because the patch hasn't really had a
> fair chance to-date, despite Tomas' efforts and quick responses to all
> review comments.
>
> At this stage though, all I can say is that I'll make every effort to
> keep reviewing it, and I hope Tomas will persist, so that it has a
> better chance in PG12.
Yes, let's please keep going on this patch.
--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com
+ As you are, so once was I. As I am, so you will be. +
+ Ancient Roman grave inscription +
^ permalink raw reply [nested|flat] 268+ messages in thread
* Re: [HACKERS] PATCH: multivariate histograms and MCV lists
@ 2018-04-07 20:44 Tomas Vondra <tomas.vondra@2ndquadrant.com>
parent: Dean Rasheed <dean.a.rasheed@gmail.com>
1 sibling, 0 replies; 268+ messages in thread
From: Tomas Vondra @ 2018-04-07 20:44 UTC (permalink / raw)
To: Dean Rasheed <dean.a.rasheed@gmail.com>; Bruce Momjian <bruce@momjian.us>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; Andres Freund <andres@anarazel.de>; Thomas Munro <thomas.munro@enterprisedb.com>; Mark Dilger <hornschnorter@gmail.com>; Adrien Nayrat <adrien.nayrat@dalibo.com>; pgsql-hackers
On 04/07/2018 07:52 PM, Dean Rasheed wrote:
> On 7 April 2018 at 15:12, Bruce Momjian <bruce@momjian.us> wrote:
>> Uh, where are we on this patch? It isn't going to make it into PG 11?
>> Feature development for this has been going on for years.
>
> Unfortunately, I think there's no way that this will be ready for
> PG11. So far, I have only read parts of the patch, focusing mainly on
> the planner changes, and how it will make use of the new statistics. I
> think there are still issues to work out in that area, although I
> haven't read the latest version yet, I have some doubts about the new
> approach described.
>
> Looking back at the history of this patch, it appears to have moved
> through all 4 PG11 commitfests with fairly minimal review, never
> becoming Ready for Committer. That's a real shame because I agree that
> it's an important feature, but IMO it's not yet in a committable
> state. I feel bad saying that, because the patch hasn't really had a
> fair chance to-date, despite Tomas' efforts and quick responses to all
> review comments.
>
Well, yeah. The free fall through all PG11 commitfests is somewhat
demotivating :-/
I certainly agree the patch is not committable in the current state. I
don't think it's in terrible shape, but I'm sure there are still some
dubious parts. I certainly know about a few.
> At this stage though, all I can say is that I'll make every effort to
> keep reviewing it, and I hope Tomas will persist, so that it has a
> better chance in PG12.
>
Thank you for the effort and for the reviews, of course.
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
--
2.47.3
--db7q7wsxflbz42ld--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v2] Add \pset options for boolean value display
@ 2025-10-20 10:29 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw)
The server's space-expedient choice to use 't' and 'f' to represent
boolean true and false respectively is technically understandable but
visually atrocious. Teach psql to detect these two values and print
whatever it deems is appropriate. In the interest of backward
compatability, that defaults to 't' and 'f'. However, now the user can
impose their own standards by using the newly introduced display_true
and display_false pset settings.
Author: David G. Johnston <David.G.Johnston@gmail.com>
Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++
src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++-
src/fe_utils/print.c | 4 +++
src/include/fe_utils/print.h | 2 ++
src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++
src/test/regress/sql/psql.sql | 16 +++++++++++
6 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..06f1e08d87a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-meta-command-pset-display-false">
+ <term><literal>display_false</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a false value.
+ The default is to print <literal>f</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_false 'false'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="app-psql-meta-command-pset-display-true">
+ <term><literal>display_true</literal></term>
+ <listitem>
+ <para>
+ Sets the string to be printed in place of a true value.
+ The default is to print <literal>t</literal>, as that is the value
+ transmitted by the server. For readability,
+ <literal>\pset display_true 'true'</literal> is recommended.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-meta-command-pset-expanded">
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..f7454daf6ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "border", "columns", "csv_fieldsep",
+ "display_false", "display_true", "expanded", "fieldsep",
"fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
@@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
}
+ /* 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ if (value)
+ {
+ free(popt->falsePrint);
+ popt->falsePrint = pg_strdup(value);
+ }
+ }
+
+ /* 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ if (value)
+ {
+ free(popt->truePrint);
+ popt->truePrint = pg_strdup(value);
+ }
+ }
+
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt)
popt->topt.csvFieldSep);
}
+ /* show boolean 'false' display */
+ else if (strcmp(param, "display_false") == 0)
+ {
+ printf(_("Boolean false display is \"%s\".\n"),
+ popt->falsePrint ? popt->falsePrint : "f");
+ }
+
+ /* show boolean 'true' display */
+ else if (strcmp(param, "display_true") == 0)
+ {
+ printf(_("Boolean true display is \"%s\".\n"),
+ popt->truePrint ? popt->truePrint : "t");
+ }
+
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
@@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "csv_fieldsep") == 0)
return pset_quoted_string(popt->topt.csvFieldSep);
+ else if (strcmp(param, "display_false") == 0)
+ return pset_quoted_string(popt->falsePrint ?
+ popt->falsePrint : "f");
+ else if (strcmp(param, "display_true") == 0)
+ return pset_quoted_string(popt->truePrint ?
+ popt->truePrint : "t");
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 73847d3d6b3..4d97ad2ddeb 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
+ else if (PQftype(result, c) == BOOLOID)
+ cell = (PQgetvalue(result, r, c)[0] == 't' ?
+ (opt->truePrint ? opt->truePrint : "t") :
+ (opt->falsePrint ? opt->falsePrint : "f"));
else
{
cell = PQgetvalue(result, r, c);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index c99c2ee1a31..6a6fc7e132c 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -184,6 +184,8 @@ typedef struct printQueryOpt
{
printTableOpt topt; /* the options above */
char *nullPrint; /* how to print null entities */
+ char *truePrint; /* how to print boolean true values */
+ char *falsePrint; /* how to print boolean false values */
char *title; /* override title */
char **footers; /* override footer (default is "(xx rows)") */
bool translate_header; /* do gettext on column headers */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index fa8984ffe0d..c8f3932edf0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -445,6 +445,8 @@ environment value
border 1
columns 0
csv_fieldsep ','
+display_false 'f'
+display_true 't'
expanded off
fieldsep '|'
fieldsep_zero off
@@ -464,6 +466,36 @@ unicode_border_linestyle single
unicode_column_linestyle single
unicode_header_linestyle single
xheader_width full
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+ n | t | f
+--------+------+-------
+ (null) | true | false
+(1 row)
+
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+ n | t | f
+---+---+---
+ | t | f
+(1 row)
+
+deallocate q;
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f064e4f5456..dcdbd4fc020 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
-- show all pset options
\pset
+-- test the simple display substitution settings
+prepare q as select null as n, true as t, false as f;
+\pset null '(null)'
+\pset display_true 'true'
+\pset display_false 'false'
+execute q;
+\pset null
+\pset display_true
+\pset display_false
+execute q;
+\pset null ''
+\pset display_true 't'
+\pset display_false 'f'
+execute q;
+deallocate q;
+
-- test multi-line headers, wrapping, and newline indicators
-- in aligned, unaligned, and wrapped formats
prepare q as select array_to_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 --
2018-04-01 16:07 Re: [HACKERS] PATCH: multivariate histograms and MCV lists Tomas Vondra <tomas.vondra@2ndquadrant.com>
2018-04-07 14:12 ` Bruce Momjian <bruce@momjian.us>
2018-04-07 17:52 ` Dean Rasheed <dean.a.rasheed@gmail.com>
2018-04-07 18:34 ` Bruce Momjian <bruce@momjian.us>
2018-04-07 20:44 ` Tomas Vondra <tomas.vondra@2ndquadrant.com>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <alvherre@kurilemu.de>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox