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