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