Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1vAqoX-006Zgp-BC for pgsql-hackers@arkaria.postgresql.org; Mon, 20 Oct 2025 14:24:44 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1vAqoW-000rje-9R for pgsql-hackers@arkaria.postgresql.org; Mon, 20 Oct 2025 14:24:43 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1vAqoV-000rjW-V4 for pgsql-hackers@lists.postgresql.org; Mon, 20 Oct 2025 14:24:43 +0000 Received: from fhigh-b8-smtp.messagingengine.com ([202.12.124.159]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vAqoS-003FAf-21 for pgsql-hackers@lists.postgresql.org; Mon, 20 Oct 2025 14:24:42 +0000 Received: from phl-compute-04.internal (phl-compute-04.internal [10.202.2.44]) by mailfhigh.stl.internal (Postfix) with ESMTP id EC0577A010D; Mon, 20 Oct 2025 10:24:38 -0400 (EDT) Received: from phl-mailfrontend-02 ([10.202.2.163]) by phl-compute-04.internal (MEProxy); Mon, 20 Oct 2025 10:24:39 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kurilemu.de; h= cc:cc:content-transfer-encoding:content-type:content-type:date :date:from:from:in-reply-to:in-reply-to:message-id:mime-version :reply-to:subject:subject:to:to; s=fm1; t=1760970278; x= 1761056678; bh=es0zdWwfmR2lvrhInc+z44G7wnShODz0kHw4iUNj2CA=; b=g pTwxTz3Ji0GIXTx7aYMl90tG9vhG8d8M2JMnx1QxxVRu4SMJqjEq9JlE54lN+hMx JAf0/LDrancRIyETGtxvk2eg8BcovUF32yuGYSVfsJjlcgK4/cH5BPhWVtW005uH DLtgiC2gG/TUDyKrMdBFOHvOEAek+Ba1AeVi51/5XqSYXiyf7BN/QjXTzoN8TE8M +p+aAYjvYSujsP/ih3IC24TpfR6HLq/huA2GSxABqzy1O81mshfT9xyHfGiQXH3L waM0hcHYoxsv4PuDj+TNlVa3RhMDf8YwkzcmXui1+QTQbPcb8v7oFdx7ED8VsJZK IW1HXK4ZqodFYBtjvcVrQ== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-transfer-encoding :content-type:content-type:date:date:feedback-id:feedback-id :from:from:in-reply-to:in-reply-to:message-id:mime-version :reply-to:subject:subject:to:to:x-me-proxy:x-me-sender :x-me-sender:x-sasl-enc; s=fm2; t=1760970278; x=1761056678; bh=e s0zdWwfmR2lvrhInc+z44G7wnShODz0kHw4iUNj2CA=; b=eGTu/8EAEE8WkTyKl 0dtDtDuYmsc/2M+WoShbHbohLI8wmxQzrqPI+/C8Ar1FQFq0ZO8SuQgMkGbGYzHp pB/1DU9389VCQhZvbCneooEDz9rKa0djADSy5b5T5XszOXbLvBV/A10HnpwfJgaP hQI1xfaHQelC2vsXU6e/voYcFcTAMK1dv7yRyFO2KOyRCZ++JNWn07zxKeLQZOUe msjdWLTVY/aRs5RKrrNNi8SMQsZpVNfYYjiEPzMqUnnxuVLyJxXpyqXYeP4IlN1J +KZGNICqADsQsTuOD/yNjICcDXx1BdYwP2Tu2xdWwwT1YQfXd+SEgE6B7nqQ+P4F 8+4Lg== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeeffedrtdeggddufeektdehucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfurfetoffkrfgpnffqhgenuceu rghilhhouhhtmecufedttdenucesvcftvggtihhpihgvnhhtshculddquddttddmnecujf gurhepfffhvfevuffkgggtugfgjgesmhekreertddtjeenucfhrhhomheplmhlvhgrrhho ucfjvghrrhgvrhgruceorghlvhhhvghrrhgvsehkuhhrihhlvghmuhdruggvqeenucggtf frrghtthgvrhhnpeegudetudejheduveevgeehjeegleevveevvdeutdejtdekuefhheeh geevtdejteenucffohhmrghinhepvghnthgvrhhprhhishgvuggsrdgtohhmnecuvehluh hsthgvrhfuihiivgeptdenucfrrghrrghmpehmrghilhhfrhhomheprghlvhhhvghrrhgv sehkuhhrihhlvghmuhdruggvpdhnsggprhgtphhtthhopedvpdhmohguvgepshhmthhpoh huthdprhgtphhtthhopegurghvihgurdhgrdhjohhhnhhsthhonhesghhmrghilhdrtgho mhdprhgtphhtthhopehpghhsqhhlqdhhrggtkhgvrhhssehlihhsthhsrdhpohhsthhgrh gvshhqlhdrohhrgh X-ME-Proxy: Feedback-ID: ie3de48e3:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Mon, 20 Oct 2025 10:24:38 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kurilemu.de; s=schmee; t=1760970276; bh=JX0Su/V3kg3g5h+Aew90m5fcbbh19RrSdOkoEEZawpQ=; h=Date:From:To:Cc:Subject:In-Reply-To:From; b=Ol+h33ZV0qZpKpLZg5PbhhGSEZlZkQP9XcrUQYN5OOJcGiocG5SfsK+GUdLjbqwTG Nmwt913SAu3WongigqvsJCev7iDktqxdFQ+gQhsZ9N21/VY/5KMWc25WNOdwVdPcnt El1oh/8EU/oS8PXC/T58eBkh3VgJDXvbXuvjRFgOdtDKBWeiFId0k5f8xUwc496I9+ M67lDeZFFNWdQwVckjgjSon4J9xew8Mf2zbFJ00FnEoZjRejdUNIR15atsoXV/+rvs ezxWUcjjZYVd+dnLpL73kCdlurzN04T6mYIFCXYCZn791G5zAw/XiLI4Y6Oja84t3M pDk9o7DMNoF9Q== Received: by schmee.kurilemu.internal (Postfix, from userid 1000) id DF5B26A; Mon, 20 Oct 2025 16:24:36 +0200 (CEST) Date: Mon, 20 Oct 2025 16:24:36 +0200 From: =?utf-8?Q?=C3=81lvaro?= Herrera To: "David G. Johnston" Cc: PostgreSQL Hackers Subject: Re: Add \pset options for boolean value display Message-ID: <202510201419.mpror4maizbg@alvherre.pgsql> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="db7q7wsxflbz42ld" Content-Disposition: inline Content-Transfer-Encoding: 8bit In-Reply-To: List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --db7q7wsxflbz42ld Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: 8bit On 2025-Jun-24, David G. Johnston wrote: > v1, Ready aside from bike-shedding the name. Here's v2 after some kibitzing. What do you think? -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ --db7q7wsxflbz42ld Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="v2-0001-Add-pset-options-for-boolean-value-display.patch" From ab14c69835836ff70c7193ff016e683ec8de9608 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Herrera?= Date: Mon, 20 Oct 2025 12:29:34 +0200 Subject: [PATCH v2] Add \pset options for boolean value display The server's space-expedient choice to use 't' and 'f' to represent boolean true and false respectively is technically understandable but visually atrocious. Teach psql to detect these two values and print whatever it deems is appropriate. In the interest of backward compatability, that defaults to 't' and 'f'. However, now the user can impose their own standards by using the newly introduced display_true and display_false pset settings. Author: David G. Johnston Discussion: https://postgr.es/m/CAKFQuwYts3vnfQ5AoKhEaKMTNMfJ443MW2kFswKwzn7fiofkrw@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++++++++++ src/bin/psql/command.c | 43 +++++++++++++++++++++++++++++- src/fe_utils/print.c | 4 +++ src/include/fe_utils/print.h | 2 ++ src/test/regress/expected/psql.out | 32 ++++++++++++++++++++++ src/test/regress/sql/psql.sql | 16 +++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1a339600bc4..06f1e08d87a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3099,6 +3099,30 @@ SELECT $1 \parse stmt1 + + display_false + + + Sets the string to be printed in place of a false value. + The default is to print f, as that is the value + transmitted by the server. For readability, + \pset display_false 'false' is recommended. + + + + + + display_true + + + Sets the string to be printed in place of a true value. + The default is to print t, as that is the value + transmitted by the server. For readability, + \pset display_true 'true' is recommended. + + + + expanded (or x) diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cc602087db2..f7454daf6ed 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -2709,7 +2709,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch) int i; static const char *const my_list[] = { - "border", "columns", "csv_fieldsep", "expanded", "fieldsep", + "border", "columns", "csv_fieldsep", + "display_false", "display_true", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", @@ -5300,6 +5301,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) } } + /* 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + if (value) + { + free(popt->falsePrint); + popt->falsePrint = pg_strdup(value); + } + } + + /* 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + if (value) + { + free(popt->truePrint); + popt->truePrint = pg_strdup(value); + } + } + /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5474,6 +5495,20 @@ printPsetInfo(const char *param, printQueryOpt *popt) popt->topt.csvFieldSep); } + /* show boolean 'false' display */ + else if (strcmp(param, "display_false") == 0) + { + printf(_("Boolean false display is \"%s\".\n"), + popt->falsePrint ? popt->falsePrint : "f"); + } + + /* show boolean 'true' display */ + else if (strcmp(param, "display_true") == 0) + { + printf(_("Boolean true display is \"%s\".\n"), + popt->truePrint ? popt->truePrint : "t"); + } + /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { @@ -5743,6 +5778,12 @@ pset_value_string(const char *param, printQueryOpt *popt) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); + else if (strcmp(param, "display_false") == 0) + return pset_quoted_string(popt->falsePrint ? + popt->falsePrint : "f"); + else if (strcmp(param, "display_true") == 0) + return pset_quoted_string(popt->truePrint ? + popt->truePrint : "t"); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 73847d3d6b3..4d97ad2ddeb 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3775,6 +3775,10 @@ printQuery(const PGresult *result, const printQueryOpt *opt, if (PQgetisnull(result, r, c)) cell = opt->nullPrint ? opt->nullPrint : ""; + else if (PQftype(result, c) == BOOLOID) + cell = (PQgetvalue(result, r, c)[0] == 't' ? + (opt->truePrint ? opt->truePrint : "t") : + (opt->falsePrint ? opt->falsePrint : "f")); else { cell = PQgetvalue(result, r, c); diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h index c99c2ee1a31..6a6fc7e132c 100644 --- a/src/include/fe_utils/print.h +++ b/src/include/fe_utils/print.h @@ -184,6 +184,8 @@ typedef struct printQueryOpt { printTableOpt topt; /* the options above */ char *nullPrint; /* how to print null entities */ + char *truePrint; /* how to print boolean true values */ + char *falsePrint; /* how to print boolean false values */ char *title; /* override title */ char **footers; /* override footer (default is "(xx rows)") */ bool translate_header; /* do gettext on column headers */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index fa8984ffe0d..c8f3932edf0 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -445,6 +445,8 @@ environment value border 1 columns 0 csv_fieldsep ',' +display_false 'f' +display_true 't' expanded off fieldsep '|' fieldsep_zero off @@ -464,6 +466,36 @@ unicode_border_linestyle single unicode_column_linestyle single unicode_header_linestyle single xheader_width full +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null +\pset display_true +\pset display_false +execute q; + n | t | f +--------+------+------- + (null) | true | false +(1 row) + +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; + n | t | f +---+---+--- + | t | f +(1 row) + +deallocate q; -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index f064e4f5456..dcdbd4fc020 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -219,6 +219,22 @@ select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over' -- show all pset options \pset +-- test the simple display substitution settings +prepare q as select null as n, true as t, false as f; +\pset null '(null)' +\pset display_true 'true' +\pset display_false 'false' +execute q; +\pset null +\pset display_true +\pset display_false +execute q; +\pset null '' +\pset display_true 't' +\pset display_false 'f' +execute q; +deallocate q; + -- test multi-line headers, wrapping, and newline indicators -- in aligned, unaligned, and wrapped formats prepare q as select array_to_string(array_agg(repeat('x',2*n)),E'\n') as "ab -- 2.47.3 --db7q7wsxflbz42ld--