LCOV - code coverage report
Current view: top level - src/bin/psql - crosstabview.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 95.5 % 247 236
Test Date: 2026-07-02 19:16:38 Functions: 100.0 % 16 16
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*
       2              :  * psql - the PostgreSQL interactive terminal
       3              :  *
       4              :  * Copyright (c) 2000-2026, PostgreSQL Global Development Group
       5              :  *
       6              :  * src/bin/psql/crosstabview.c
       7              :  */
       8              : #include "postgres_fe.h"
       9              : 
      10              : #include "catalog/pg_type_d.h"
      11              : #include "common.h"
      12              : #include "common/int.h"
      13              : #include "common/logging.h"
      14              : #include "crosstabview.h"
      15              : #include "pqexpbuffer.h"
      16              : #include "psqlscanslash.h"
      17              : #include "settings.h"
      18              : 
      19              : /*
      20              :  * Value/position from the resultset that goes into the horizontal or vertical
      21              :  * crosstabview header.
      22              :  */
      23              : typedef struct _pivot_field
      24              : {
      25              :     /*
      26              :      * Pointer obtained from PQgetvalue() for colV or colH. Each distinct
      27              :      * value becomes an entry in the vertical header (colV), or horizontal
      28              :      * header (colH). A Null value is represented by a NULL pointer.
      29              :      */
      30              :     char       *name;
      31              : 
      32              :     /*
      33              :      * When a sort is requested on an alternative column, this holds
      34              :      * PQgetvalue() for the sort column corresponding to <name>. If <name>
      35              :      * appear multiple times, it's the first value in the order of the results
      36              :      * that is kept. A Null value is represented by a NULL pointer.
      37              :      */
      38              :     char       *sort_value;
      39              : 
      40              :     /*
      41              :      * Rank of this value, starting at 0. Initially, it's the relative
      42              :      * position of the first appearance of <name> in the resultset. For
      43              :      * example, if successive rows contain B,A,C,A,D then it's B:0,A:1,C:2,D:3
      44              :      * When a sort column is specified, ranks get updated in a final pass to
      45              :      * reflect the desired order.
      46              :      */
      47              :     int         rank;
      48              : } pivot_field;
      49              : 
      50              : /* Node in avl_tree */
      51              : typedef struct _avl_node
      52              : {
      53              :     /* Node contents */
      54              :     pivot_field field;
      55              : 
      56              :     /*
      57              :      * Height of this node in the tree (number of nodes on the longest path to
      58              :      * a leaf).
      59              :      */
      60              :     int         height;
      61              : 
      62              :     /*
      63              :      * Child nodes. [0] points to left subtree, [1] to right subtree. Never
      64              :      * NULL, points to the empty node avl_tree.end when no left or right
      65              :      * value.
      66              :      */
      67              :     struct _avl_node *children[2];
      68              : } avl_node;
      69              : 
      70              : /*
      71              :  * Control structure for the AVL tree (binary search tree kept
      72              :  * balanced with the AVL algorithm)
      73              :  */
      74              : typedef struct _avl_tree
      75              : {
      76              :     int         count;          /* Total number of nodes */
      77              :     avl_node   *root;           /* root of the tree */
      78              :     avl_node   *end;            /* Immutable dereferenceable empty tree */
      79              : } avl_tree;
      80              : 
      81              : 
      82              : static bool printCrosstab(const PGresult *result,
      83              :                           int num_columns, pivot_field *piv_columns, int field_for_columns,
      84              :                           int num_rows, pivot_field *piv_rows, int field_for_rows,
      85              :                           int field_for_data);
      86              : static char *displayValue(char *value, Oid ftype, char *default_null);
      87              : 
      88              : static void avlInit(avl_tree *tree);
      89              : static void avlMergeValue(avl_tree *tree, char *name, char *sort_value);
      90              : static int  avlCollectFields(avl_tree *tree, avl_node *node,
      91              :                              pivot_field *fields, int idx);
      92              : static void avlFree(avl_tree *tree, avl_node *node);
      93              : static void rankSort(int num_columns, pivot_field *piv_columns);
      94              : static int  indexOfColumn(char *arg, const PGresult *res);
      95              : static int  pivotFieldCompare(const void *a, const void *b);
      96              : static int  rankCompare(const void *a, const void *b);
      97              : 
      98              : 
      99              : /*
     100              :  * Main entry point to this module.
     101              :  *
     102              :  * Process the data from *res according to the options in pset (global),
     103              :  * to generate the horizontal and vertical headers contents,
     104              :  * then call printCrosstab() for the actual output.
     105              :  */
     106              : bool
     107           96 : PrintResultInCrosstab(const PGresult *res)
     108              : {
     109           96 :     bool        retval = false;
     110              :     avl_tree    piv_columns;
     111              :     avl_tree    piv_rows;
     112           96 :     pivot_field *array_columns = NULL;
     113           96 :     pivot_field *array_rows = NULL;
     114           96 :     int         num_columns = 0;
     115           96 :     int         num_rows = 0;
     116              :     int         field_for_rows;
     117              :     int         field_for_columns;
     118              :     int         field_for_data;
     119              :     int         sort_field_for_columns;
     120              :     int         rn;
     121              : 
     122           96 :     avlInit(&piv_rows);
     123           96 :     avlInit(&piv_columns);
     124              : 
     125           96 :     if (PQresultStatus(res) != PGRES_TUPLES_OK)
     126              :     {
     127            0 :         pg_log_error("\\crosstabview: statement did not return a result set");
     128            0 :         goto error_return;
     129              :     }
     130              : 
     131           96 :     if (PQnfields(res) < 3)
     132              :     {
     133            8 :         pg_log_error("\\crosstabview: query must return at least three columns");
     134            8 :         goto error_return;
     135              :     }
     136              : 
     137              :     /* Process first optional arg (vertical header column) */
     138           88 :     if (pset.ctv_args[0] == NULL)
     139           20 :         field_for_rows = 0;
     140              :     else
     141              :     {
     142           68 :         field_for_rows = indexOfColumn(pset.ctv_args[0], res);
     143           68 :         if (field_for_rows < 0)
     144            0 :             goto error_return;
     145              :     }
     146              : 
     147              :     /* Process second optional arg (horizontal header column) */
     148           88 :     if (pset.ctv_args[1] == NULL)
     149           20 :         field_for_columns = 1;
     150              :     else
     151              :     {
     152           68 :         field_for_columns = indexOfColumn(pset.ctv_args[1], res);
     153           68 :         if (field_for_columns < 0)
     154            4 :             goto error_return;
     155              :     }
     156              : 
     157              :     /* Insist that header columns be distinct */
     158           84 :     if (field_for_columns == field_for_rows)
     159              :     {
     160            4 :         pg_log_error("\\crosstabview: vertical and horizontal headers must be different columns");
     161            4 :         goto error_return;
     162              :     }
     163              : 
     164              :     /* Process third optional arg (data column) */
     165           80 :     if (pset.ctv_args[2] == NULL)
     166              :     {
     167              :         int         i;
     168              : 
     169              :         /*
     170              :          * If the data column was not specified, we search for the one not
     171              :          * used as either vertical or horizontal headers.  Must be exactly
     172              :          * three columns, or this won't be unique.
     173              :          */
     174           24 :         if (PQnfields(res) != 3)
     175              :         {
     176            0 :             pg_log_error("\\crosstabview: data column must be specified when query returns more than three columns");
     177            0 :             goto error_return;
     178              :         }
     179              : 
     180           24 :         field_for_data = -1;
     181           72 :         for (i = 0; i < PQnfields(res); i++)
     182              :         {
     183           72 :             if (i != field_for_rows && i != field_for_columns)
     184              :             {
     185           24 :                 field_for_data = i;
     186           24 :                 break;
     187              :             }
     188              :         }
     189              :         Assert(field_for_data >= 0);
     190              :     }
     191              :     else
     192              :     {
     193           56 :         field_for_data = indexOfColumn(pset.ctv_args[2], res);
     194           56 :         if (field_for_data < 0)
     195           12 :             goto error_return;
     196              :     }
     197              : 
     198              :     /* Process fourth optional arg (horizontal header sort column) */
     199           68 :     if (pset.ctv_args[3] == NULL)
     200           48 :         sort_field_for_columns = -1;    /* no sort column */
     201              :     else
     202              :     {
     203           20 :         sort_field_for_columns = indexOfColumn(pset.ctv_args[3], res);
     204           20 :         if (sort_field_for_columns < 0)
     205            0 :             goto error_return;
     206              :     }
     207              : 
     208              :     /*
     209              :      * First part: accumulate the names that go into the vertical and
     210              :      * horizontal headers, each into an AVL binary tree to build the set of
     211              :      * DISTINCT values.
     212              :      */
     213              : 
     214         6792 :     for (rn = 0; rn < PQntuples(res); rn++)
     215              :     {
     216              :         char       *val;
     217              :         char       *val1;
     218              : 
     219              :         /* horizontal */
     220         6728 :         val = PQgetisnull(res, rn, field_for_columns) ? NULL :
     221         6680 :             PQgetvalue(res, rn, field_for_columns);
     222         6728 :         val1 = NULL;
     223              : 
     224         6828 :         if (sort_field_for_columns >= 0 &&
     225          100 :             !PQgetisnull(res, rn, sort_field_for_columns))
     226          100 :             val1 = PQgetvalue(res, rn, sort_field_for_columns);
     227              : 
     228         6728 :         avlMergeValue(&piv_columns, val, val1);
     229              : 
     230         6728 :         if (piv_columns.count > CROSSTABVIEW_MAX_COLUMNS)
     231              :         {
     232            4 :             pg_log_error("\\crosstabview: maximum number of columns (%d) exceeded",
     233              :                          CROSSTABVIEW_MAX_COLUMNS);
     234            4 :             goto error_return;
     235              :         }
     236              : 
     237              :         /* vertical */
     238         6724 :         val = PQgetisnull(res, rn, field_for_rows) ? NULL :
     239         6704 :             PQgetvalue(res, rn, field_for_rows);
     240              : 
     241         6724 :         avlMergeValue(&piv_rows, val, NULL);
     242              :     }
     243              : 
     244              :     /*
     245              :      * Second part: Generate sorted arrays from the AVL trees.
     246              :      */
     247              : 
     248           64 :     num_columns = piv_columns.count;
     249           64 :     num_rows = piv_rows.count;
     250              : 
     251           64 :     array_columns = pg_malloc_array(pivot_field, num_columns);
     252              : 
     253           64 :     array_rows = pg_malloc_array(pivot_field, num_rows);
     254              : 
     255           64 :     avlCollectFields(&piv_columns, piv_columns.root, array_columns, 0);
     256           64 :     avlCollectFields(&piv_rows, piv_rows.root, array_rows, 0);
     257              : 
     258              :     /*
     259              :      * Third part: optionally, process the ranking data for the horizontal
     260              :      * header
     261              :      */
     262           64 :     if (sort_field_for_columns >= 0)
     263           20 :         rankSort(num_columns, array_columns);
     264              : 
     265              :     /*
     266              :      * Fourth part: print the crosstab'ed result.
     267              :      */
     268           64 :     retval = printCrosstab(res,
     269              :                            num_columns, array_columns, field_for_columns,
     270              :                            num_rows, array_rows, field_for_rows,
     271              :                            field_for_data);
     272              : 
     273           96 : error_return:
     274           96 :     avlFree(&piv_columns, piv_columns.root);
     275           96 :     avlFree(&piv_rows, piv_rows.root);
     276           96 :     pg_free(array_columns);
     277           96 :     pg_free(array_rows);
     278              : 
     279           96 :     return retval;
     280              : }
     281              : 
     282              : /*
     283              :  * Output the pivoted resultset with the printTable* functions.  Return true
     284              :  * if successful, false otherwise.
     285              :  */
     286              : static bool
     287           64 : printCrosstab(const PGresult *result,
     288              :               int num_columns, pivot_field *piv_columns, int field_for_columns,
     289              :               int num_rows, pivot_field *piv_rows, int field_for_rows,
     290              :               int field_for_data)
     291              : {
     292           64 :     printQueryOpt popt = pset.popt;
     293              :     printTableContent cont;
     294              :     int         i,
     295              :                 rn;
     296              :     char        col_align;
     297              :     int        *horiz_map;
     298           64 :     Oid         col_ftype = PQftype(result, field_for_columns);
     299           64 :     Oid         row_ftype = PQftype(result, field_for_rows);
     300           64 :     Oid         data_ftype = PQftype(result, field_for_data);
     301           64 :     bool        retval = false;
     302              : 
     303           64 :     printTableInit(&cont, &popt.topt, popt.title, num_columns + 1, num_rows);
     304              : 
     305              :     /* Step 1: set target column names (horizontal header) */
     306              : 
     307              :     /* The name of the first column is kept unchanged by the pivoting */
     308           64 :     printTableAddHeader(&cont,
     309              :                         PQfname(result, field_for_rows),
     310              :                         false,
     311           64 :                         column_type_alignment(row_ftype));
     312              : 
     313              :     /*
     314              :      * To iterate over piv_columns[] by piv_columns[].rank, create a reverse
     315              :      * map associating each piv_columns[].rank to its index in piv_columns.
     316              :      * This avoids an O(N^2) loop later.
     317              :      */
     318           64 :     horiz_map = pg_malloc_array(int, num_columns);
     319          332 :     for (i = 0; i < num_columns; i++)
     320          268 :         horiz_map[piv_columns[i].rank] = i;
     321              : 
     322              :     /*
     323              :      * The display alignment depends on its PQftype().
     324              :      */
     325           64 :     col_align = column_type_alignment(data_ftype);
     326              : 
     327          332 :     for (i = 0; i < num_columns; i++)
     328              :     {
     329              :         char       *colname;
     330              : 
     331          268 :         colname = displayValue(piv_columns[horiz_map[i]].name, col_ftype, "");
     332              : 
     333          268 :         printTableAddHeader(&cont, colname, false, col_align);
     334              :     }
     335           64 :     pg_free(horiz_map);
     336              : 
     337              :     /* Step 2: set row names in the first output column (vertical header) */
     338          228 :     for (i = 0; i < num_rows; i++)
     339              :     {
     340          164 :         int         k = piv_rows[i].rank;
     341          164 :         int         idx = k * (num_columns + 1);
     342              : 
     343          164 :         cont.cells[idx] = displayValue(piv_rows[i].name, row_ftype, "");
     344              :     }
     345           64 :     cont.cellsadded = num_rows * (num_columns + 1);
     346              : 
     347              :     /*
     348              :      * Step 3: fill in the content cells.
     349              :      */
     350          380 :     for (rn = 0; rn < PQntuples(result); rn++)
     351              :     {
     352              :         int         row_number;
     353              :         int         col_number;
     354              :         pivot_field *rp,
     355              :                    *cp;
     356              :         pivot_field elt;
     357              : 
     358              :         /* Find target row */
     359          324 :         if (!PQgetisnull(result, rn, field_for_rows))
     360          304 :             elt.name = PQgetvalue(result, rn, field_for_rows);
     361              :         else
     362           20 :             elt.name = NULL;
     363          324 :         rp = (pivot_field *) bsearch(&elt,
     364              :                                      piv_rows,
     365              :                                      num_rows,
     366              :                                      sizeof(pivot_field),
     367              :                                      pivotFieldCompare);
     368              :         Assert(rp != NULL);
     369          324 :         row_number = rp->rank;
     370              : 
     371              :         /* Find target column */
     372          324 :         if (!PQgetisnull(result, rn, field_for_columns))
     373          276 :             elt.name = PQgetvalue(result, rn, field_for_columns);
     374              :         else
     375           48 :             elt.name = NULL;
     376              : 
     377          324 :         cp = (pivot_field *) bsearch(&elt,
     378              :                                      piv_columns,
     379              :                                      num_columns,
     380              :                                      sizeof(pivot_field),
     381              :                                      pivotFieldCompare);
     382              :         Assert(cp != NULL);
     383          324 :         col_number = cp->rank;
     384              : 
     385              :         /* Place value into cell */
     386          324 :         if (col_number >= 0 && row_number >= 0)
     387              :         {
     388              :             int         idx;
     389              :             char       *value;
     390              : 
     391              :             /* index into the cont.cells array */
     392          324 :             idx = 1 + col_number + row_number * (num_columns + 1);
     393              : 
     394              :             /*
     395              :              * If the cell already contains a value, raise an error.
     396              :              */
     397          324 :             if (cont.cells[idx] != NULL)
     398              :             {
     399            8 :                 pg_log_error("\\crosstabview: query result contains multiple data values for row \"%s\", column \"%s\"",
     400              :                              displayValue(rp->name, row_ftype, "(null)"),
     401              :                              displayValue(cp->name, col_ftype, "(null)"));
     402            8 :                 goto error;
     403              :             }
     404              : 
     405          316 :             if (PQgetisnull(result, rn, field_for_data))
     406           12 :                 value = NULL;
     407              :             else
     408          304 :                 value = PQgetvalue(result, rn, field_for_data);
     409          316 :             cont.cells[idx] = displayValue(value, data_ftype, "");
     410              :         }
     411              :     }
     412              : 
     413              :     /*
     414              :      * The non-initialized cells must be set to an empty string for the print
     415              :      * functions
     416              :      */
     417          820 :     for (i = 0; i < cont.cellsadded; i++)
     418              :     {
     419          764 :         if (cont.cells[i] == NULL)
     420          336 :             cont.cells[i] = "";
     421              :     }
     422              : 
     423           56 :     printTable(&cont, pset.queryFout, false, pset.logfile);
     424           56 :     retval = true;
     425              : 
     426           64 : error:
     427           64 :     printTableCleanup(&cont);
     428              : 
     429           64 :     return retval;
     430              : }
     431              : 
     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          764 : displayValue(char *value, Oid ftype, char *default_null)
     440              : {
     441          764 :     printQueryOpt popt = pset.popt;
     442              : 
     443          764 :     if (value == NULL)
     444           64 :         value = popt.nullPrint ? popt.nullPrint : default_null;
     445          700 :     else if (ftype == BOOLOID)
     446              :     {
     447           48 :         if (value[0] == 't' && popt.truePrint)
     448           28 :             value = popt.truePrint;
     449           20 :         else if (value[0] == 'f' && popt.falsePrint)
     450           20 :             value = popt.falsePrint;
     451              :     }
     452              : 
     453          764 :     return value;
     454              : }
     455              : 
     456              : /*
     457              :  * The avl* functions below provide a minimalistic implementation of AVL binary
     458              :  * trees, to efficiently collect the distinct values that will form the horizontal
     459              :  * and vertical headers. It only supports adding new values, no removal or even
     460              :  * search.
     461              :  */
     462              : static void
     463          192 : avlInit(avl_tree *tree)
     464              : {
     465          192 :     tree->end = pg_malloc0_object(avl_node);
     466          192 :     tree->end->children[0] = tree->end->children[1] = tree->end;
     467          192 :     tree->count = 0;
     468          192 :     tree->root = tree->end;
     469          192 : }
     470              : 
     471              : /* Deallocate recursively an AVL tree, starting from node */
     472              : static void
     473        13292 : avlFree(avl_tree *tree, avl_node *node)
     474              : {
     475        13292 :     if (node->children[0] != tree->end)
     476              :     {
     477         6712 :         avlFree(tree, node->children[0]);
     478         6712 :         pg_free(node->children[0]);
     479              :     }
     480        13292 :     if (node->children[1] != tree->end)
     481              :     {
     482         6388 :         avlFree(tree, node->children[1]);
     483         6388 :         pg_free(node->children[1]);
     484              :     }
     485        13292 :     if (node == tree->root)
     486              :     {
     487              :         /* free the root separately as it's not child of anything */
     488          192 :         if (node != tree->end)
     489          136 :             pg_free(node);
     490              :         /* free the tree->end struct only once and when all else is freed */
     491          192 :         pg_free(tree->end);
     492              :     }
     493        13292 : }
     494              : 
     495              : /* Set the height to 1 plus the greatest of left and right heights */
     496              : static void
     497       150380 : avlUpdateHeight(avl_node *n)
     498              : {
     499       150380 :     n->height = 1 + (n->children[0]->height > n->children[1]->height ?
     500       150380 :                      n->children[0]->height :
     501              :                      n->children[1]->height);
     502       150380 : }
     503              : 
     504              : /* Rotate a subtree left (dir=0) or right (dir=1). Not recursive */
     505              : static avl_node *
     506        16148 : avlRotate(avl_node **current, int dir)
     507              : {
     508        16148 :     avl_node   *before = *current;
     509        16148 :     avl_node   *after = (*current)->children[dir];
     510              : 
     511        16148 :     *current = after;
     512        16148 :     before->children[dir] = after->children[!dir];
     513        16148 :     avlUpdateHeight(before);
     514        16148 :     after->children[!dir] = before;
     515              : 
     516        16148 :     return after;
     517              : }
     518              : 
     519              : static int
     520       146124 : avlBalance(avl_node *n)
     521              : {
     522       146124 :     return n->children[0]->height - n->children[1]->height;
     523              : }
     524              : 
     525              : /*
     526              :  * After an insertion, possibly rebalance the tree so that the left and right
     527              :  * node heights don't differ by more than 1.
     528              :  * May update *node.
     529              :  */
     530              : static void
     531       134232 : avlAdjustBalance(avl_tree *tree, avl_node **node)
     532              : {
     533       134232 :     avl_node   *current = *node;
     534       134232 :     int         b = avlBalance(current) / 2;
     535              : 
     536       134232 :     if (b != 0)
     537              :     {
     538        11892 :         int         dir = (1 - b) / 2;
     539              : 
     540        11892 :         if (avlBalance(current->children[dir]) == -b)
     541         4256 :             avlRotate(&current->children[dir], !dir);
     542        11892 :         current = avlRotate(node, dir);
     543              :     }
     544       134232 :     if (current != tree->end)
     545       134232 :         avlUpdateHeight(current);
     546       134232 : }
     547              : 
     548              : /*
     549              :  * Insert a new value/field, starting from *node, reaching the correct position
     550              :  * in the tree by recursion.  Possibly rebalance the tree and possibly update
     551              :  * *node.  Do nothing if the value is already present in the tree.
     552              :  */
     553              : static void
     554       147684 : avlInsertNode(avl_tree *tree, avl_node **node, pivot_field field)
     555              : {
     556       147684 :     avl_node   *current = *node;
     557              : 
     558       147684 :     if (current == tree->end)
     559              :     {
     560        13236 :         avl_node   *new_node = pg_malloc_object(avl_node);
     561              : 
     562        13236 :         new_node->height = 1;
     563        13236 :         new_node->field = field;
     564        13236 :         new_node->children[0] = new_node->children[1] = tree->end;
     565        13236 :         tree->count++;
     566        13236 :         *node = new_node;
     567              :     }
     568              :     else
     569              :     {
     570       134448 :         int         cmp = pivotFieldCompare(&field, &current->field);
     571              : 
     572       134448 :         if (cmp != 0)
     573              :         {
     574       134232 :             avlInsertNode(tree,
     575              :                           cmp > 0 ? &current->children[1] : &current->children[0],
     576              :                           field);
     577       134232 :             avlAdjustBalance(tree, node);
     578              :         }
     579              :     }
     580       147684 : }
     581              : 
     582              : /* Insert the value into the AVL tree, if it does not preexist */
     583              : static void
     584        13452 : avlMergeValue(avl_tree *tree, char *name, char *sort_value)
     585              : {
     586              :     pivot_field field;
     587              : 
     588        13452 :     field.name = name;
     589        13452 :     field.rank = tree->count;
     590        13452 :     field.sort_value = sort_value;
     591        13452 :     avlInsertNode(tree, &tree->root, field);
     592        13452 : }
     593              : 
     594              : /*
     595              :  * Recursively extract node values into the names array, in sorted order with a
     596              :  * left-to-right tree traversal.
     597              :  * Return the next candidate offset to write into the names array.
     598              :  * fields[] must be preallocated to hold tree->count entries
     599              :  */
     600              : static int
     601          992 : avlCollectFields(avl_tree *tree, avl_node *node, pivot_field *fields, int idx)
     602              : {
     603          992 :     if (node == tree->end)
     604          560 :         return idx;
     605              : 
     606          432 :     idx = avlCollectFields(tree, node->children[0], fields, idx);
     607          432 :     fields[idx] = node->field;
     608          432 :     return avlCollectFields(tree, node->children[1], fields, idx + 1);
     609              : }
     610              : 
     611              : static void
     612           20 : rankSort(int num_columns, pivot_field *piv_columns)
     613              : {
     614              :     int        *hmap;           /* [[offset in piv_columns, rank], ...for
     615              :                                  * every header entry] */
     616              :     int         i;
     617              : 
     618           20 :     hmap = pg_malloc_array(int, num_columns * 2);
     619          104 :     for (i = 0; i < num_columns; i++)
     620              :     {
     621           84 :         char       *val = piv_columns[i].sort_value;
     622              : 
     623              :         /* ranking information is valid if non null and matches /^-?\d+$/ */
     624           84 :         if (val &&
     625           84 :             ((*val == '-' &&
     626            0 :               strspn(val + 1, "0123456789") == strlen(val + 1)) ||
     627           84 :              strspn(val, "0123456789") == strlen(val)))
     628              :         {
     629           84 :             hmap[i * 2] = atoi(val);
     630           84 :             hmap[i * 2 + 1] = i;
     631              :         }
     632              :         else
     633              :         {
     634              :             /* invalid rank information ignored (equivalent to rank 0) */
     635            0 :             hmap[i * 2] = 0;
     636            0 :             hmap[i * 2 + 1] = i;
     637              :         }
     638              :     }
     639              : 
     640           20 :     qsort(hmap, num_columns, sizeof(int) * 2, rankCompare);
     641              : 
     642          104 :     for (i = 0; i < num_columns; i++)
     643              :     {
     644           84 :         piv_columns[hmap[i * 2 + 1]].rank = i;
     645              :     }
     646              : 
     647           20 :     pg_free(hmap);
     648           20 : }
     649              : 
     650              : /*
     651              :  * Look up a column reference, which can be either:
     652              :  * - a number from 1 to PQnfields(res)
     653              :  * - a column name matching one of PQfname(res,...)
     654              :  *
     655              :  * Returns zero-based column number, or -1 if not found or ambiguous.
     656              :  *
     657              :  * Note: may modify contents of "arg" string.
     658              :  */
     659              : static int
     660          212 : indexOfColumn(char *arg, const PGresult *res)
     661              : {
     662              :     int         idx;
     663              : 
     664          212 :     if (arg[0] && strspn(arg, "0123456789") == strlen(arg))
     665              :     {
     666              :         /* if arg contains only digits, it's a column number */
     667           64 :         idx = atoi(arg) - 1;
     668           64 :         if (idx < 0 || idx >= PQnfields(res))
     669              :         {
     670            4 :             pg_log_error("\\crosstabview: column number %d is out of range 1..%d",
     671              :                          idx + 1, PQnfields(res));
     672            4 :             return -1;
     673              :         }
     674              :     }
     675              :     else
     676              :     {
     677              :         int         i;
     678              : 
     679              :         /*
     680              :          * Dequote and downcase the column name.  By checking for all-digits
     681              :          * before doing this, we can ensure that a quoted name is treated as a
     682              :          * name even if it's all digits.
     683              :          */
     684          148 :         dequote_downcase_identifier(arg, true, pset.encoding);
     685              : 
     686              :         /* Now look for match(es) among res' column names */
     687          148 :         idx = -1;
     688          688 :         for (i = 0; i < PQnfields(res); i++)
     689              :         {
     690          540 :             if (strcmp(arg, PQfname(res, i)) == 0)
     691              :             {
     692          136 :                 if (idx >= 0)
     693              :                 {
     694              :                     /* another idx was already found for the same name */
     695            0 :                     pg_log_error("\\crosstabview: ambiguous column name: \"%s\"", arg);
     696            0 :                     return -1;
     697              :                 }
     698          136 :                 idx = i;
     699              :             }
     700              :         }
     701          148 :         if (idx == -1)
     702              :         {
     703           12 :             pg_log_error("\\crosstabview: column name not found: \"%s\"", arg);
     704           12 :             return -1;
     705              :         }
     706              :     }
     707              : 
     708          196 :     return idx;
     709              : }
     710              : 
     711              : /*
     712              :  * Value comparator for vertical and horizontal headers
     713              :  * used for deduplication only.
     714              :  * - null values are considered equal
     715              :  * - non-null < null
     716              :  * - non-null values are compared with strcmp()
     717              :  */
     718              : static int
     719       135624 : pivotFieldCompare(const void *a, const void *b)
     720              : {
     721       135624 :     const pivot_field *pa = (const pivot_field *) a;
     722       135624 :     const pivot_field *pb = (const pivot_field *) b;
     723              : 
     724              :     /* test null values */
     725       135624 :     if (!pb->name)
     726          140 :         return pa->name ? -1 : 0;
     727       135484 :     else if (!pa->name)
     728          124 :         return 1;
     729              : 
     730              :     /* non-null values */
     731       135360 :     return strcmp(pa->name, pb->name);
     732              : }
     733              : 
     734              : static int
     735           96 : rankCompare(const void *a, const void *b)
     736              : {
     737           96 :     return pg_cmp_s32(*(const int *) a, *(const int *) b);
     738              : }
        

Generated by: LCOV version 2.0-1