diff --git a/src/bin/psql/crosstabview.c b/src/bin/psql/crosstabview.c index 111e8823bdb..b59437e41eb 100644 --- a/src/bin/psql/crosstabview.c +++ b/src/bin/psql/crosstabview.c @@ -7,6 +7,7 @@ */ #include "postgres_fe.h" +#include "catalog/pg_type_d.h" #include "common.h" #include "common/int.h" #include "common/logging.h" @@ -82,6 +83,8 @@ static bool printCrosstab(const PGresult *result, int num_columns, pivot_field *piv_columns, int field_for_columns, int num_rows, pivot_field *piv_rows, int field_for_rows, int field_for_data); +static char *displayValue(char *value, Oid ftype, char *default_null); + static void avlInit(avl_tree *tree); static void avlMergeValue(avl_tree *tree, char *name, char *sort_value); static int avlCollectFields(avl_tree *tree, avl_node *node, @@ -292,6 +295,9 @@ printCrosstab(const PGresult *result, rn; char col_align; int *horiz_map; + Oid col_ftype = PQftype(result, field_for_columns); + Oid row_ftype = PQftype(result, field_for_rows); + Oid data_ftype = PQftype(result, field_for_data); bool retval = false; printTableInit(&cont, &popt.topt, popt.title, num_columns + 1, num_rows); @@ -302,8 +308,7 @@ printCrosstab(const PGresult *result, printTableAddHeader(&cont, PQfname(result, field_for_rows), false, - column_type_alignment(PQftype(result, - field_for_rows))); + column_type_alignment(row_ftype)); /* * To iterate over piv_columns[] by piv_columns[].rank, create a reverse @@ -317,15 +322,13 @@ printCrosstab(const PGresult *result, /* * The display alignment depends on its PQftype(). */ - col_align = column_type_alignment(PQftype(result, field_for_data)); + col_align = column_type_alignment(data_ftype); for (i = 0; i < num_columns; i++) { char *colname; - colname = piv_columns[horiz_map[i]].name ? - piv_columns[horiz_map[i]].name : - (popt.nullPrint ? popt.nullPrint : ""); + colname = displayValue(piv_columns[horiz_map[i]].name, col_ftype, ""); printTableAddHeader(&cont, colname, false, col_align); } @@ -335,10 +338,9 @@ printCrosstab(const PGresult *result, for (i = 0; i < num_rows; i++) { int k = piv_rows[i].rank; + int idx = k * (num_columns + 1); - cont.cells[k * (num_columns + 1)] = piv_rows[i].name ? - piv_rows[i].name : - (popt.nullPrint ? popt.nullPrint : ""); + cont.cells[idx] = displayValue(piv_rows[i].name, row_ftype, ""); } cont.cellsadded = num_rows * (num_columns + 1); @@ -384,6 +386,7 @@ printCrosstab(const PGresult *result, if (col_number >= 0 && row_number >= 0) { int idx; + char *value; /* index into the cont.cells array */ idx = 1 + col_number + row_number * (num_columns + 1); @@ -394,16 +397,16 @@ printCrosstab(const PGresult *result, if (cont.cells[idx] != NULL) { pg_log_error("\\crosstabview: query result contains multiple data values for row \"%s\", column \"%s\"", - rp->name ? rp->name : - (popt.nullPrint ? popt.nullPrint : "(null)"), - cp->name ? cp->name : - (popt.nullPrint ? popt.nullPrint : "(null)")); + displayValue(rp->name, row_ftype, "(null)"), + displayValue(cp->name, col_ftype, "(null)")); goto error; } - cont.cells[idx] = !PQgetisnull(result, rn, field_for_data) ? - PQgetvalue(result, rn, field_for_data) : - (popt.nullPrint ? popt.nullPrint : ""); + if (PQgetisnull(result, rn, field_for_data)) + value = NULL; + else + value = PQgetvalue(result, rn, field_for_data); + cont.cells[idx] = displayValue(value, data_ftype, ""); } } @@ -426,6 +429,30 @@ error: return retval; } +/* + * Return the display representation of one cell value in \crosstabview, + * following pset substitutions. + * + * The returned pointer is not to be freed. + */ +static char * +displayValue(char *value, Oid ftype, char *default_null) +{ + printQueryOpt popt = pset.popt; + + if (value == NULL) + value = popt.nullPrint ? popt.nullPrint : default_null; + else if (ftype == BOOLOID) + { + if (value[0] == 't' && popt.truePrint) + value = popt.truePrint; + else if (value[0] == 'f' && popt.falsePrint) + value = popt.falsePrint; + } + + return value; +} + /* * The avl* functions below provide a minimalistic implementation of AVL binary * trees, to efficiently collect the distinct values that will form the horizontal diff --git a/src/test/regress/expected/psql_crosstab.out b/src/test/regress/expected/psql_crosstab.out index e09e3310165..e32d79d38b0 100644 --- a/src/test/regress/expected/psql_crosstab.out +++ b/src/test/regress/expected/psql_crosstab.out @@ -136,6 +136,35 @@ GROUP BY v, h ORDER BY h,v | | | | -3 | (3 rows) +\pset null '' +-- boolean display +\pset display_true 'Aye' +\pset display_false 'Nay' +\pset null 'Wut' +with rows (display_bools, col_key, val) as (values + (false, false, false), + (true, true, true), + (null, null, null), + (null, true, false), + (null, false, true), + (true, null, false), + (false, null, true) +) select * from rows + \crosstabview display_bools col_key val + display_bools | Nay | Aye | Wut +---------------+-----+-----+----- + Nay | Nay | | Aye + Aye | | Aye | Nay + Wut | Aye | Nay | Wut +(3 rows) + +with rows (tst, col, val) as (values + (true, null, 42), (true, null, 142857) +) select * from rows + \crosstabview tst col +\crosstabview: query result contains multiple data values for row "Aye", column "Wut" +\pset display_true 't' +\pset display_false 'f' \pset null '' -- refer to columns by position SELECT v,h,string_agg(i::text, E'\n'), string_agg(c, E'\n') diff --git a/src/test/regress/sql/psql_crosstab.sql b/src/test/regress/sql/psql_crosstab.sql index 5a4511389de..82a73981d5d 100644 --- a/src/test/regress/sql/psql_crosstab.sql +++ b/src/test/regress/sql/psql_crosstab.sql @@ -69,6 +69,30 @@ GROUP BY v, h ORDER BY h,v \crosstabview v h i \pset null '' +-- boolean display +\pset display_true 'Aye' +\pset display_false 'Nay' +\pset null 'Wut' +with rows (display_bools, col_key, val) as (values + (false, false, false), + (true, true, true), + (null, null, null), + (null, true, false), + (null, false, true), + (true, null, false), + (false, null, true) +) select * from rows + \crosstabview display_bools col_key val + +with rows (tst, col, val) as (values + (true, null, 42), (true, null, 142857) +) select * from rows + \crosstabview tst col + +\pset display_true 't' +\pset display_false 'f' +\pset null '' + -- refer to columns by position SELECT v,h,string_agg(i::text, E'\n'), string_agg(c, E'\n') FROM ctv_data GROUP BY v, h ORDER BY h,v