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