agora inbox for [email protected]help / color / mirror / Atom feed
[PATCH v2] Add \pset options for boolean value display 264+ messages / 2 participants [nested] [flat]
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v2] Add \pset options for boolean value display @ 2025-10-20 10:29 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Álvaro Herrera @ 2025-10-20 10:29 UTC (permalink / raw) The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston <[email protected]> Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 </listitem> </varlistentry> + <varlistentry id="app-psql-meta-command-pset-display-false"> + <term><literal>display_false</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a false value. + The default is to print <literal>f</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_false 'false'</literal> is recommended. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-meta-command-pset-display-true"> + <term><literal>display_true</literal></term> + <listitem> + <para> + Sets the string to be printed in place of a true value. + The default is to print <literal>t</literal>, as that is the value + transmitted by the server. For readability, + <literal>\pset display_true 'true'</literal> is recommended. + </para> + </listitem> + </varlistentry> + <varlistentry id="app-psql-meta-command-pset-expanded"> <term><literal>expanded</literal> (or <literal>x</literal>)</term> <listitem> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld-- ^ permalink raw reply [nested|flat] 264+ messages in thread
* [PATCH v3 1/7] Track retired system procedures in pg_retired.dat @ 2026-01-29 23:47 Mark Wong <[email protected]> 0 siblings, 0 replies; 264+ messages in thread From: Mark Wong @ 2026-01-29 23:47 UTC (permalink / raw) System procedures may be removed from time to time. Keep track of previously used OIDs to prevent potential conflicts from reusing OIDs after an upgrade. --- src/include/catalog/pg_retired.dat | 20 ++++++++++++++++++++ src/include/catalog/renumber_oids.pl | 9 +++++++++ src/include/catalog/unused_oids | 7 +++++++ 3 files changed, 36 insertions(+) create mode 100644 src/include/catalog/pg_retired.dat diff --git a/src/include/catalog/pg_retired.dat b/src/include/catalog/pg_retired.dat new file mode 100644 index 00000000000..413b68ad1cd --- /dev/null +++ b/src/include/catalog/pg_retired.dat @@ -0,0 +1,20 @@ +#---------------------------------------------------------------------- +# +# pg_retired.dat +# Entries that have been removed from pg_proc.dat with OIDs that are +# are not to be reused. +# +# Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group +# Portions Copyright (c) 1994, Regents of the University of California +# +# src/include/catalog/pg_retired.dat +# +#---------------------------------------------------------------------- + +[ + +# What's primarily important here is knowing which OIDs are not to be reused. +# At the same time, it may be good to be reminded what procedure was associated +# with it. + +] diff --git a/src/include/catalog/renumber_oids.pl b/src/include/catalog/renumber_oids.pl index f6e25d0f1e1..7489be02a50 100755 --- a/src/include/catalog/renumber_oids.pl +++ b/src/include/catalog/renumber_oids.pl @@ -68,6 +68,15 @@ my $oids = Catalog::FindAllOidsFromHeaders(@header_files); my %oidhash; @oidhash{@$oids} = undef; +# Also include OIDs listed in pg_retired.dat to prevent new OIDs to be mapped +# to OIDs that have been retired. +my $retired_data = Catalog::ParseData('pg_retired.dat', undef, 0); +foreach my $row (@$retired_data) +{ + $oidhash{ $row->{oid} } = undef + if ref $row eq 'HASH' && defined $row->{oid}; +} + # Select new OIDs for existing OIDs in the mapped range. # We do this first so that we preserve the ordering of the mapped OIDs # (for reproducibility's sake), and so that if we fail due to running out diff --git a/src/include/catalog/unused_oids b/src/include/catalog/unused_oids index 147fba5e209..aeb485d9b5e 100755 --- a/src/include/catalog/unused_oids +++ b/src/include/catalog/unused_oids @@ -32,6 +32,13 @@ my @input_files = glob("pg_*.h"); my $oids = Catalog::FindAllOidsFromHeaders(@input_files); +# Also exclude OIDs listed in pg_retired.dat +my $retired_data = Catalog::ParseData('pg_retired.dat', undef, 0); +foreach my $row (@$retired_data) +{ + push @{$oids}, $row->{oid} if ref $row eq 'HASH' && defined $row->{oid}; +} + # Also push FirstGenbkiObjectId to serve as a terminator for the last gap. my $FirstGenbkiObjectId = Catalog::FindDefinedSymbol('access/transam.h', '..', 'FirstGenbkiObjectId'); -- 2.43.0 --zd8Z8rlPOcTi77n/ Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v3-0002-Handle-pg_get_ruledef-default-args-in-system_func.patch" ^ permalink raw reply [nested|flat] 264+ messages in thread
end of thread, other threads:[~2026-01-29 23:47 UTC | newest] Thread overview: 264+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2025-10-20 10:29 [PATCH v2] Add \pset options for boolean value display Álvaro Herrera <[email protected]> 2026-01-29 23:47 [PATCH v3 1/7] Track retired system procedures in pg_retired.dat Mark Wong <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox