Skip to content

Commit 4df5fe3

Browse files
committed
Make crosstabview honor boolean/null display settings
psql's \pset display_true/false settings, added by commit 645cb44, affect normal query output, but not \crosstabview. As a result, boolean values used anywhere in crosstab output were always shown as "t" or "f", which is inconsistent. Change \crosstabview so that the configured values are displayed instead. While at it, make \crosstabview print the \pset null string, if any, in cells for which the query produces a NULL value. Cells for which the query produces no value continue to have the empty string. This is an oversight in the aboriginal \crosstabview commit, c09b18f. Add a regression test covering all of this. Author: Chao Li <lic@highgo.com> Reported-by: Chao Li <lic@highgo.com> Reviewed-by: David G. Johnston <david.g.johnston@gmail.com> Reviewed-by: Álvaro Herrera <alvherre@kurilemu.de> Backpatch: none needed Discussion: https://postgr.es/m/B5E6F0A5-4B48-46D0-B5EB-CF8F8CC7D07D@gmail.com
1 parent dac3660 commit 4df5fe3

3 files changed

Lines changed: 96 additions & 16 deletions

File tree

src/bin/psql/crosstabview.c

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88
#include "postgres_fe.h"
99

10+
#include "catalog/pg_type_d.h"
1011
#include "common.h"
1112
#include "common/int.h"
1213
#include "common/logging.h"
@@ -82,6 +83,8 @@ static bool printCrosstab(const PGresult *result,
8283
int num_columns, pivot_field *piv_columns, int field_for_columns,
8384
int num_rows, pivot_field *piv_rows, int field_for_rows,
8485
int field_for_data);
86+
static char *displayValue(char *value, Oid ftype, char *default_null);
87+
8588
static void avlInit(avl_tree *tree);
8689
static void avlMergeValue(avl_tree *tree, char *name, char *sort_value);
8790
static int avlCollectFields(avl_tree *tree, avl_node *node,
@@ -292,6 +295,9 @@ printCrosstab(const PGresult *result,
292295
rn;
293296
char col_align;
294297
int *horiz_map;
298+
Oid col_ftype = PQftype(result, field_for_columns);
299+
Oid row_ftype = PQftype(result, field_for_rows);
300+
Oid data_ftype = PQftype(result, field_for_data);
295301
bool retval = false;
296302

297303
printTableInit(&cont, &popt.topt, popt.title, num_columns + 1, num_rows);
@@ -302,8 +308,7 @@ printCrosstab(const PGresult *result,
302308
printTableAddHeader(&cont,
303309
PQfname(result, field_for_rows),
304310
false,
305-
column_type_alignment(PQftype(result,
306-
field_for_rows)));
311+
column_type_alignment(row_ftype));
307312

308313
/*
309314
* To iterate over piv_columns[] by piv_columns[].rank, create a reverse
@@ -317,15 +322,13 @@ printCrosstab(const PGresult *result,
317322
/*
318323
* The display alignment depends on its PQftype().
319324
*/
320-
col_align = column_type_alignment(PQftype(result, field_for_data));
325+
col_align = column_type_alignment(data_ftype);
321326

322327
for (i = 0; i < num_columns; i++)
323328
{
324329
char *colname;
325330

326-
colname = piv_columns[horiz_map[i]].name ?
327-
piv_columns[horiz_map[i]].name :
328-
(popt.nullPrint ? popt.nullPrint : "");
331+
colname = displayValue(piv_columns[horiz_map[i]].name, col_ftype, "");
329332

330333
printTableAddHeader(&cont, colname, false, col_align);
331334
}
@@ -335,10 +338,9 @@ printCrosstab(const PGresult *result,
335338
for (i = 0; i < num_rows; i++)
336339
{
337340
int k = piv_rows[i].rank;
341+
int idx = k * (num_columns + 1);
338342

339-
cont.cells[k * (num_columns + 1)] = piv_rows[i].name ?
340-
piv_rows[i].name :
341-
(popt.nullPrint ? popt.nullPrint : "");
343+
cont.cells[idx] = displayValue(piv_rows[i].name, row_ftype, "");
342344
}
343345
cont.cellsadded = num_rows * (num_columns + 1);
344346

@@ -384,6 +386,7 @@ printCrosstab(const PGresult *result,
384386
if (col_number >= 0 && row_number >= 0)
385387
{
386388
int idx;
389+
char *value;
387390

388391
/* index into the cont.cells array */
389392
idx = 1 + col_number + row_number * (num_columns + 1);
@@ -394,16 +397,16 @@ printCrosstab(const PGresult *result,
394397
if (cont.cells[idx] != NULL)
395398
{
396399
pg_log_error("\\crosstabview: query result contains multiple data values for row \"%s\", column \"%s\"",
397-
rp->name ? rp->name :
398-
(popt.nullPrint ? popt.nullPrint : "(null)"),
399-
cp->name ? cp->name :
400-
(popt.nullPrint ? popt.nullPrint : "(null)"));
400+
displayValue(rp->name, row_ftype, "(null)"),
401+
displayValue(cp->name, col_ftype, "(null)"));
401402
goto error;
402403
}
403404

404-
cont.cells[idx] = !PQgetisnull(result, rn, field_for_data) ?
405-
PQgetvalue(result, rn, field_for_data) :
406-
(popt.nullPrint ? popt.nullPrint : "");
405+
if (PQgetisnull(result, rn, field_for_data))
406+
value = NULL;
407+
else
408+
value = PQgetvalue(result, rn, field_for_data);
409+
cont.cells[idx] = displayValue(value, data_ftype, "");
407410
}
408411
}
409412

@@ -426,6 +429,30 @@ printCrosstab(const PGresult *result,
426429
return retval;
427430
}
428431

432+
/*
433+
* Return the display representation of one cell value in \crosstabview,
434+
* following pset substitutions.
435+
*
436+
* The returned pointer is not to be freed.
437+
*/
438+
static char *
439+
displayValue(char *value, Oid ftype, char *default_null)
440+
{
441+
printQueryOpt popt = pset.popt;
442+
443+
if (value == NULL)
444+
value = popt.nullPrint ? popt.nullPrint : default_null;
445+
else if (ftype == BOOLOID)
446+
{
447+
if (value[0] == 't' && popt.truePrint)
448+
value = popt.truePrint;
449+
else if (value[0] == 'f' && popt.falsePrint)
450+
value = popt.falsePrint;
451+
}
452+
453+
return value;
454+
}
455+
429456
/*
430457
* The avl* functions below provide a minimalistic implementation of AVL binary
431458
* trees, to efficiently collect the distinct values that will form the horizontal

src/test/regress/expected/psql_crosstab.out

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,35 @@ GROUP BY v, h ORDER BY h,v
136136
| | | | -3 |
137137
(3 rows)
138138

139+
\pset null ''
140+
-- boolean display
141+
\pset display_true 'Aye'
142+
\pset display_false 'Nay'
143+
\pset null 'Wut'
144+
with rows (display_bools, col_key, val) as (values
145+
(false, false, false),
146+
(true, true, true),
147+
(null, null, null),
148+
(null, true, false),
149+
(null, false, true),
150+
(true, null, false),
151+
(false, null, true)
152+
) select * from rows
153+
\crosstabview display_bools col_key val
154+
display_bools | Nay | Aye | Wut
155+
---------------+-----+-----+-----
156+
Nay | Nay | | Aye
157+
Aye | | Aye | Nay
158+
Wut | Aye | Nay | Wut
159+
(3 rows)
160+
161+
with rows (tst, col, val) as (values
162+
(true, null, 42), (true, null, 142857)
163+
) select * from rows
164+
\crosstabview tst col
165+
\crosstabview: query result contains multiple data values for row "Aye", column "Wut"
166+
\pset display_true 't'
167+
\pset display_false 'f'
139168
\pset null ''
140169
-- refer to columns by position
141170
SELECT v,h,string_agg(i::text, E'\n'), string_agg(c, E'\n')

src/test/regress/sql/psql_crosstab.sql

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,30 @@ GROUP BY v, h ORDER BY h,v
6969
\crosstabview v h i
7070
\pset null ''
7171

72+
-- boolean display
73+
\pset display_true 'Aye'
74+
\pset display_false 'Nay'
75+
\pset null 'Wut'
76+
with rows (display_bools, col_key, val) as (values
77+
(false, false, false),
78+
(true, true, true),
79+
(null, null, null),
80+
(null, true, false),
81+
(null, false, true),
82+
(true, null, false),
83+
(false, null, true)
84+
) select * from rows
85+
\crosstabview display_bools col_key val
86+
87+
with rows (tst, col, val) as (values
88+
(true, null, 42), (true, null, 142857)
89+
) select * from rows
90+
\crosstabview tst col
91+
92+
\pset display_true 't'
93+
\pset display_false 'f'
94+
\pset null ''
95+
7296
-- refer to columns by position
7397
SELECT v,h,string_agg(i::text, E'\n'), string_agg(c, E'\n')
7498
FROM ctv_data GROUP BY v, h ORDER BY h,v

0 commit comments

Comments
 (0)