1 /*-------------------------------------------------------------------------
2 *
3 * fe-print.c
4 * functions for pretty-printing query results
5 *
6 * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
8 *
9 * These functions were formerly part of fe-exec.c, but they
10 * didn't really belong there.
11 *
12 * IDENTIFICATION
13 * src/interfaces/libpq/fe-print.c
14 *
15 *-------------------------------------------------------------------------
16 */
17 #include "postgres_fe.h"
18
19 #include <signal.h>
20
21 #ifdef WIN32
22 #include "win32.h"
23 #else
24 #include <unistd.h>
25 #include <sys/ioctl.h>
26 #endif
27
28 #ifdef HAVE_TERMIOS_H
29 #include <termios.h>
30 #else
31 #ifndef WIN32
32 #include <sys/termios.h>
33 #endif
34 #endif
35
36 #include "libpq-fe.h"
37 #include "libpq-int.h"
38
39 #define PQmblenBounded(s, e) strnlen(s, PQmblen(s, e))
40
41 static bool do_field(const PQprintOpt *po, const PGresult *res,
42 const int i, const int j, const int fs_len,
43 char **fields,
44 const int nFields, const char **fieldNames,
45 unsigned char *fieldNotNum, int *fieldMax,
46 const int fieldMaxLen, FILE *fout);
47 static char *do_header(FILE *fout, const PQprintOpt *po, const int nFields,
48 int *fieldMax, const char **fieldNames, unsigned char *fieldNotNum,
49 const int fs_len, const PGresult *res);
50 static void output_row(FILE *fout, const PQprintOpt *po, const int nFields, char **fields,
51 unsigned char *fieldNotNum, int *fieldMax, char *border,
52 const int row_index);
53 static void fill(int length, int max, char filler, FILE *fp);
54
55 /*
56 * PQprint()
57 *
58 * Format results of a query for printing.
59 *
60 * PQprintOpt is a typedef (structure) that contains
61 * various flags and options. consult libpq-fe.h for
62 * details
63 *
64 * This function should probably be removed sometime since psql
65 * doesn't use it anymore. It is unclear to what extent this is used
66 * by external clients, however.
67 */
68 void
PQprint(FILE * fout,const PGresult * res,const PQprintOpt * po)69 PQprint(FILE *fout, const PGresult *res, const PQprintOpt *po)
70 {
71 int nFields;
72
73 nFields = PQnfields(res);
74
75 if (nFields > 0)
76 { /* only print rows with at least 1 field. */
77 int i,
78 j;
79 int nTups;
80 int *fieldMax = NULL; /* in case we don't use them */
81 unsigned char *fieldNotNum = NULL;
82 char *border = NULL;
83 char **fields = NULL;
84 const char **fieldNames = NULL;
85 int fieldMaxLen = 0;
86 int numFieldName;
87 int fs_len = strlen(po->fieldSep);
88 int total_line_length = 0;
89 bool usePipe = false;
90 char *pagerenv;
91
92 #if defined(ENABLE_THREAD_SAFETY) && !defined(WIN32)
93 sigset_t osigset;
94 bool sigpipe_masked = false;
95 bool sigpipe_pending;
96 #endif
97 #if !defined(ENABLE_THREAD_SAFETY) && !defined(WIN32)
98 pqsigfunc oldsigpipehandler = NULL;
99 #endif
100
101 #ifdef TIOCGWINSZ
102 struct winsize screen_size;
103 #else
104 struct winsize
105 {
106 int ws_row;
107 int ws_col;
108 } screen_size;
109 #endif
110
111 nTups = PQntuples(res);
112 fieldNames = (const char **) calloc(nFields, sizeof(char *));
113 fieldNotNum = (unsigned char *) calloc(nFields, 1);
114 fieldMax = (int *) calloc(nFields, sizeof(int));
115 if (!fieldNames || !fieldNotNum || !fieldMax)
116 {
117 fprintf(stderr, libpq_gettext("out of memory\n"));
118 goto exit;
119 }
120 for (numFieldName = 0;
121 po->fieldName && po->fieldName[numFieldName];
122 numFieldName++)
123 ;
124 for (j = 0; j < nFields; j++)
125 {
126 int len;
127 const char *s = (j < numFieldName && po->fieldName[j][0]) ?
128 po->fieldName[j] : PQfname(res, j);
129
130 fieldNames[j] = s;
131 len = s ? strlen(s) : 0;
132 fieldMax[j] = len;
133 len += fs_len;
134 if (len > fieldMaxLen)
135 fieldMaxLen = len;
136 total_line_length += len;
137 }
138
139 total_line_length += nFields * strlen(po->fieldSep) + 1;
140
141 if (fout == NULL)
142 fout = stdout;
143 if (po->pager && fout == stdout && isatty(fileno(stdin)) &&
144 isatty(fileno(stdout)))
145 {
146 /*
147 * If we think there'll be more than one screen of output, try to
148 * pipe to the pager program.
149 */
150 #ifdef TIOCGWINSZ
151 if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) == -1 ||
152 screen_size.ws_col == 0 ||
153 screen_size.ws_row == 0)
154 {
155 screen_size.ws_row = 24;
156 screen_size.ws_col = 80;
157 }
158 #else
159 screen_size.ws_row = 24;
160 screen_size.ws_col = 80;
161 #endif
162
163 /*
164 * Since this function is no longer used by psql, we don't examine
165 * PSQL_PAGER. It's possible that the hypothetical external users
166 * of the function would like that to happen, but in the name of
167 * backwards compatibility, we'll stick to just examining PAGER.
168 */
169 pagerenv = getenv("PAGER");
170 /* if PAGER is unset, empty or all-white-space, don't use pager */
171 if (pagerenv != NULL &&
172 strspn(pagerenv, " \t\r\n") != strlen(pagerenv) &&
173 !po->html3 &&
174 ((po->expanded &&
175 nTups * (nFields + 1) >= screen_size.ws_row) ||
176 (!po->expanded &&
177 nTups * (total_line_length / screen_size.ws_col + 1) *
178 (1 + (po->standard != 0)) >= screen_size.ws_row -
179 (po->header != 0) *
180 (total_line_length / screen_size.ws_col + 1) * 2
181 - (po->header != 0) * 2 /* row count and newline */
182 )))
183 {
184 fout = popen(pagerenv, "w");
185 if (fout)
186 {
187 usePipe = true;
188 #ifndef WIN32
189 #ifdef ENABLE_THREAD_SAFETY
190 if (pq_block_sigpipe(&osigset, &sigpipe_pending) == 0)
191 sigpipe_masked = true;
192 #else
193 oldsigpipehandler = pqsignal(SIGPIPE, SIG_IGN);
194 #endif /* ENABLE_THREAD_SAFETY */
195 #endif /* WIN32 */
196 }
197 else
198 fout = stdout;
199 }
200 }
201
202 if (!po->expanded && (po->align || po->html3))
203 {
204 fields = (char **) calloc((size_t) nTups + 1,
205 nFields * sizeof(char *));
206 if (!fields)
207 {
208 fprintf(stderr, libpq_gettext("out of memory\n"));
209 goto exit;
210 }
211 }
212 else if (po->header && !po->html3)
213 {
214 if (po->expanded)
215 {
216 if (po->align)
217 fprintf(fout, libpq_gettext("%-*s%s Value\n"),
218 fieldMaxLen - fs_len, libpq_gettext("Field"), po->fieldSep);
219 else
220 fprintf(fout, libpq_gettext("%s%sValue\n"), libpq_gettext("Field"), po->fieldSep);
221 }
222 else
223 {
224 int len = 0;
225
226 for (j = 0; j < nFields; j++)
227 {
228 const char *s = fieldNames[j];
229
230 fputs(s, fout);
231 len += strlen(s) + fs_len;
232 if ((j + 1) < nFields)
233 fputs(po->fieldSep, fout);
234 }
235 fputc('\n', fout);
236 for (len -= fs_len; len--; fputc('-', fout));
237 fputc('\n', fout);
238 }
239 }
240 if (po->expanded && po->html3)
241 {
242 if (po->caption)
243 fprintf(fout, "<center><h2>%s</h2></center>\n", po->caption);
244 else
245 fprintf(fout,
246 "<center><h2>"
247 "Query retrieved %d rows * %d fields"
248 "</h2></center>\n",
249 nTups, nFields);
250 }
251 for (i = 0; i < nTups; i++)
252 {
253 if (po->expanded)
254 {
255 if (po->html3)
256 fprintf(fout,
257 "<table %s><caption align=\"top\">%d</caption>\n",
258 po->tableOpt ? po->tableOpt : "", i);
259 else
260 fprintf(fout, libpq_gettext("-- RECORD %d --\n"), i);
261 }
262 for (j = 0; j < nFields; j++)
263 {
264 if (!do_field(po, res, i, j, fs_len, fields, nFields,
265 fieldNames, fieldNotNum,
266 fieldMax, fieldMaxLen, fout))
267 goto exit;
268 }
269 if (po->html3 && po->expanded)
270 fputs("</table>\n", fout);
271 }
272 if (!po->expanded && (po->align || po->html3))
273 {
274 if (po->html3)
275 {
276 if (po->header)
277 {
278 if (po->caption)
279 fprintf(fout,
280 "<table %s><caption align=\"top\">%s</caption>\n",
281 po->tableOpt ? po->tableOpt : "",
282 po->caption);
283 else
284 fprintf(fout,
285 "<table %s><caption align=\"top\">"
286 "Retrieved %d rows * %d fields"
287 "</caption>\n",
288 po->tableOpt ? po->tableOpt : "", nTups, nFields);
289 }
290 else
291 fprintf(fout, "<table %s>", po->tableOpt ? po->tableOpt : "");
292 }
293 if (po->header)
294 border = do_header(fout, po, nFields, fieldMax, fieldNames,
295 fieldNotNum, fs_len, res);
296 for (i = 0; i < nTups; i++)
297 output_row(fout, po, nFields, fields,
298 fieldNotNum, fieldMax, border, i);
299 }
300 if (po->header && !po->html3)
301 fprintf(fout, "(%d row%s)\n\n", PQntuples(res),
302 (PQntuples(res) == 1) ? "" : "s");
303 if (po->html3 && !po->expanded)
304 fputs("</table>\n", fout);
305
306 exit:
307 if (fieldMax)
308 free(fieldMax);
309 if (fieldNotNum)
310 free(fieldNotNum);
311 if (border)
312 free(border);
313 if (fields)
314 {
315 /* if calloc succeeded, this shouldn't overflow size_t */
316 size_t numfields = ((size_t) nTups + 1) * (size_t) nFields;
317
318 while (numfields-- > 0)
319 {
320 if (fields[numfields])
321 free(fields[numfields]);
322 }
323 free(fields);
324 }
325 if (fieldNames)
326 free((void *) fieldNames);
327 if (usePipe)
328 {
329 #ifdef WIN32
330 _pclose(fout);
331 #else
332 pclose(fout);
333
334 #ifdef ENABLE_THREAD_SAFETY
335 /* we can't easily verify if EPIPE occurred, so say it did */
336 if (sigpipe_masked)
337 pq_reset_sigpipe(&osigset, sigpipe_pending, true);
338 #else
339 pqsignal(SIGPIPE, oldsigpipehandler);
340 #endif /* ENABLE_THREAD_SAFETY */
341 #endif /* WIN32 */
342 }
343 }
344 }
345
346
347 static bool
do_field(const PQprintOpt * po,const PGresult * res,const int i,const int j,const int fs_len,char ** fields,const int nFields,char const ** fieldNames,unsigned char * fieldNotNum,int * fieldMax,const int fieldMaxLen,FILE * fout)348 do_field(const PQprintOpt *po, const PGresult *res,
349 const int i, const int j, const int fs_len,
350 char **fields,
351 const int nFields, char const **fieldNames,
352 unsigned char *fieldNotNum, int *fieldMax,
353 const int fieldMaxLen, FILE *fout)
354 {
355 const char *pval,
356 *p;
357 int plen;
358 bool skipit;
359
360 plen = PQgetlength(res, i, j);
361 pval = PQgetvalue(res, i, j);
362
363 if (plen < 1 || !pval || !*pval)
364 {
365 if (po->align || po->expanded)
366 skipit = true;
367 else
368 {
369 skipit = false;
370 goto efield;
371 }
372 }
373 else
374 skipit = false;
375
376 if (!skipit)
377 {
378 if (po->align && !fieldNotNum[j])
379 {
380 /* Detect whether field contains non-numeric data */
381 char ch = '0';
382
383 for (p = pval; *p; p += PQmblenBounded(p, res->client_encoding))
384 {
385 ch = *p;
386 if (!((ch >= '0' && ch <= '9') ||
387 ch == '.' ||
388 ch == 'E' ||
389 ch == 'e' ||
390 ch == ' ' ||
391 ch == '-'))
392 {
393 fieldNotNum[j] = 1;
394 break;
395 }
396 }
397
398 /*
399 * Above loop will believe E in first column is numeric; also, we
400 * insist on a digit in the last column for a numeric. This test
401 * is still not bulletproof but it handles most cases.
402 */
403 if (*pval == 'E' || *pval == 'e' ||
404 !(ch >= '0' && ch <= '9'))
405 fieldNotNum[j] = 1;
406 }
407
408 if (!po->expanded && (po->align || po->html3))
409 {
410 if (plen > fieldMax[j])
411 fieldMax[j] = plen;
412 if (!(fields[i * nFields + j] = (char *) malloc(plen + 1)))
413 {
414 fprintf(stderr, libpq_gettext("out of memory\n"));
415 return false;
416 }
417 strcpy(fields[i * nFields + j], pval);
418 }
419 else
420 {
421 if (po->expanded)
422 {
423 if (po->html3)
424 fprintf(fout,
425 "<tr><td align=\"left\"><b>%s</b></td>"
426 "<td align=\"%s\">%s</td></tr>\n",
427 fieldNames[j],
428 fieldNotNum[j] ? "left" : "right",
429 pval);
430 else
431 {
432 if (po->align)
433 fprintf(fout,
434 "%-*s%s %s\n",
435 fieldMaxLen - fs_len, fieldNames[j],
436 po->fieldSep,
437 pval);
438 else
439 fprintf(fout,
440 "%s%s%s\n",
441 fieldNames[j], po->fieldSep, pval);
442 }
443 }
444 else
445 {
446 if (!po->html3)
447 {
448 fputs(pval, fout);
449 efield:
450 if ((j + 1) < nFields)
451 fputs(po->fieldSep, fout);
452 else
453 fputc('\n', fout);
454 }
455 }
456 }
457 }
458 return true;
459 }
460
461
462 static char *
do_header(FILE * fout,const PQprintOpt * po,const int nFields,int * fieldMax,const char ** fieldNames,unsigned char * fieldNotNum,const int fs_len,const PGresult * res)463 do_header(FILE *fout, const PQprintOpt *po, const int nFields, int *fieldMax,
464 const char **fieldNames, unsigned char *fieldNotNum,
465 const int fs_len, const PGresult *res)
466 {
467 int j; /* for loop index */
468 char *border = NULL;
469
470 if (po->html3)
471 fputs("<tr>", fout);
472 else
473 {
474 int tot = 0;
475 int n = 0;
476 char *p = NULL;
477
478 for (; n < nFields; n++)
479 tot += fieldMax[n] + fs_len + (po->standard ? 2 : 0);
480 if (po->standard)
481 tot += fs_len * 2 + 2;
482 border = malloc(tot + 1);
483 if (!border)
484 {
485 fprintf(stderr, libpq_gettext("out of memory\n"));
486 return NULL;
487 }
488 p = border;
489 if (po->standard)
490 {
491 char *fs = po->fieldSep;
492
493 while (*fs++)
494 *p++ = '+';
495 }
496 for (j = 0; j < nFields; j++)
497 {
498 int len;
499
500 for (len = fieldMax[j] + (po->standard ? 2 : 0); len--; *p++ = '-');
501 if (po->standard || (j + 1) < nFields)
502 {
503 char *fs = po->fieldSep;
504
505 while (*fs++)
506 *p++ = '+';
507 }
508 }
509 *p = '\0';
510 if (po->standard)
511 fprintf(fout, "%s\n", border);
512 }
513 if (po->standard)
514 fputs(po->fieldSep, fout);
515 for (j = 0; j < nFields; j++)
516 {
517 const char *s = PQfname(res, j);
518
519 if (po->html3)
520 {
521 fprintf(fout, "<th align=\"%s\">%s</th>",
522 fieldNotNum[j] ? "left" : "right", fieldNames[j]);
523 }
524 else
525 {
526 int n = strlen(s);
527
528 if (n > fieldMax[j])
529 fieldMax[j] = n;
530 if (po->standard)
531 fprintf(fout,
532 fieldNotNum[j] ? " %-*s " : " %*s ",
533 fieldMax[j], s);
534 else
535 fprintf(fout, fieldNotNum[j] ? "%-*s" : "%*s", fieldMax[j], s);
536 if (po->standard || (j + 1) < nFields)
537 fputs(po->fieldSep, fout);
538 }
539 }
540 if (po->html3)
541 fputs("</tr>\n", fout);
542 else
543 fprintf(fout, "\n%s\n", border);
544 return border;
545 }
546
547
548 static void
output_row(FILE * fout,const PQprintOpt * po,const int nFields,char ** fields,unsigned char * fieldNotNum,int * fieldMax,char * border,const int row_index)549 output_row(FILE *fout, const PQprintOpt *po, const int nFields, char **fields,
550 unsigned char *fieldNotNum, int *fieldMax, char *border,
551 const int row_index)
552 {
553 int field_index; /* for loop index */
554
555 if (po->html3)
556 fputs("<tr>", fout);
557 else if (po->standard)
558 fputs(po->fieldSep, fout);
559 for (field_index = 0; field_index < nFields; field_index++)
560 {
561 char *p = fields[row_index * nFields + field_index];
562
563 if (po->html3)
564 fprintf(fout, "<td align=\"%s\">%s</td>",
565 fieldNotNum[field_index] ? "left" : "right", p ? p : "");
566 else
567 {
568 fprintf(fout,
569 fieldNotNum[field_index] ?
570 (po->standard ? " %-*s " : "%-*s") :
571 (po->standard ? " %*s " : "%*s"),
572 fieldMax[field_index],
573 p ? p : "");
574 if (po->standard || field_index + 1 < nFields)
575 fputs(po->fieldSep, fout);
576 }
577 }
578 if (po->html3)
579 fputs("</tr>", fout);
580 else if (po->standard)
581 fprintf(fout, "\n%s", border);
582 fputc('\n', fout);
583 }
584
585
586
587 /*
588 * really old printing routines
589 */
590
591 void
PQdisplayTuples(const PGresult * res,FILE * fp,int fillAlign,const char * fieldSep,int printHeader,int quiet)592 PQdisplayTuples(const PGresult *res,
593 FILE *fp, /* where to send the output */
594 int fillAlign, /* pad the fields with spaces */
595 const char *fieldSep, /* field separator */
596 int printHeader, /* display headers? */
597 int quiet
598 )
599 {
600 #define DEFAULT_FIELD_SEP " "
601
602 int i,
603 j;
604 int nFields;
605 int nTuples;
606 int *fLength = NULL;
607
608 if (fieldSep == NULL)
609 fieldSep = DEFAULT_FIELD_SEP;
610
611 /* Get some useful info about the results */
612 nFields = PQnfields(res);
613 nTuples = PQntuples(res);
614
615 if (fp == NULL)
616 fp = stdout;
617
618 /* Figure the field lengths to align to */
619 /* will be somewhat time consuming for very large results */
620 if (fillAlign)
621 {
622 fLength = (int *) malloc(nFields * sizeof(int));
623 if (!fLength)
624 {
625 fprintf(stderr, libpq_gettext("out of memory\n"));
626 return;
627 }
628
629 for (j = 0; j < nFields; j++)
630 {
631 fLength[j] = strlen(PQfname(res, j));
632 for (i = 0; i < nTuples; i++)
633 {
634 int flen = PQgetlength(res, i, j);
635
636 if (flen > fLength[j])
637 fLength[j] = flen;
638 }
639 }
640 }
641
642 if (printHeader)
643 {
644 /* first, print out the attribute names */
645 for (i = 0; i < nFields; i++)
646 {
647 fputs(PQfname(res, i), fp);
648 if (fillAlign)
649 fill(strlen(PQfname(res, i)), fLength[i], ' ', fp);
650 fputs(fieldSep, fp);
651 }
652 fprintf(fp, "\n");
653
654 /* Underline the attribute names */
655 for (i = 0; i < nFields; i++)
656 {
657 if (fillAlign)
658 fill(0, fLength[i], '-', fp);
659 fputs(fieldSep, fp);
660 }
661 fprintf(fp, "\n");
662 }
663
664 /* next, print out the instances */
665 for (i = 0; i < nTuples; i++)
666 {
667 for (j = 0; j < nFields; j++)
668 {
669 fprintf(fp, "%s", PQgetvalue(res, i, j));
670 if (fillAlign)
671 fill(strlen(PQgetvalue(res, i, j)), fLength[j], ' ', fp);
672 fputs(fieldSep, fp);
673 }
674 fprintf(fp, "\n");
675 }
676
677 if (!quiet)
678 fprintf(fp, "\nQuery returned %d row%s.\n", PQntuples(res),
679 (PQntuples(res) == 1) ? "" : "s");
680
681 fflush(fp);
682
683 if (fLength)
684 free(fLength);
685 }
686
687
688
689 void
PQprintTuples(const PGresult * res,FILE * fout,int PrintAttNames,int TerseOutput,int colWidth)690 PQprintTuples(const PGresult *res,
691 FILE *fout, /* output stream */
692 int PrintAttNames, /* print attribute names or not */
693 int TerseOutput, /* delimiter bars or not? */
694 int colWidth /* width of column, if 0, use variable width */
695 )
696 {
697 int nFields;
698 int nTups;
699 int i,
700 j;
701 char formatString[80];
702 char *tborder = NULL;
703
704 nFields = PQnfields(res);
705 nTups = PQntuples(res);
706
707 if (colWidth > 0)
708 sprintf(formatString, "%%s %%-%ds", colWidth);
709 else
710 sprintf(formatString, "%%s %%s");
711
712 if (nFields > 0)
713 { /* only print rows with at least 1 field. */
714
715 if (!TerseOutput)
716 {
717 int width;
718
719 width = nFields * 14;
720 tborder = (char *) malloc(width + 1);
721 if (!tborder)
722 {
723 fprintf(stderr, libpq_gettext("out of memory\n"));
724 return;
725 }
726 for (i = 0; i < width; i++)
727 tborder[i] = '-';
728 tborder[width] = '\0';
729 fprintf(fout, "%s\n", tborder);
730 }
731
732 for (i = 0; i < nFields; i++)
733 {
734 if (PrintAttNames)
735 {
736 fprintf(fout, formatString,
737 TerseOutput ? "" : "|",
738 PQfname(res, i));
739 }
740 }
741
742 if (PrintAttNames)
743 {
744 if (TerseOutput)
745 fprintf(fout, "\n");
746 else
747 fprintf(fout, "|\n%s\n", tborder);
748 }
749
750 for (i = 0; i < nTups; i++)
751 {
752 for (j = 0; j < nFields; j++)
753 {
754 const char *pval = PQgetvalue(res, i, j);
755
756 fprintf(fout, formatString,
757 TerseOutput ? "" : "|",
758 pval ? pval : "");
759 }
760 if (TerseOutput)
761 fprintf(fout, "\n");
762 else
763 fprintf(fout, "|\n%s\n", tborder);
764 }
765 }
766
767 if (tborder)
768 free(tborder);
769 }
770
771
772 /* simply send out max-length number of filler characters to fp */
773
774 static void
fill(int length,int max,char filler,FILE * fp)775 fill(int length, int max, char filler, FILE *fp)
776 {
777 int count;
778
779 count = max - length;
780 while (count-- >= 0)
781 putc(filler, fp);
782 }
783