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