postgresql/src/interfaces/libpq/fe-print.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

763 lines
17 KiB
C
Raw Normal View History

1998-05-07 16:52:52 +02:00
/*-------------------------------------------------------------------------
*
* fe-print.c
1998-05-07 16:52:52 +02:00
* functions for pretty-printing query results
*
* Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
1998-05-07 16:52:52 +02:00
*
* These functions were formerly part of fe-exec.c, but they
* didn't really belong there.
*
* IDENTIFICATION
2010-09-20 22:08:53 +02:00
* src/interfaces/libpq/fe-print.c
1998-05-07 16:52:52 +02:00
*
*-------------------------------------------------------------------------
*/
#include "postgres_fe.h"
#include <signal.h>
#ifdef WIN32
#include "win32.h"
#else
1999-07-19 08:25:40 +02:00
#include <unistd.h>
#include <sys/ioctl.h>
#endif
#ifdef HAVE_TERMIOS_H
1998-05-07 16:52:52 +02:00
#include <termios.h>
#else
#ifndef WIN32
#include <sys/termios.h>
1998-05-07 16:52:52 +02:00
#endif
#endif
1998-05-07 16:52:52 +02:00
2001-01-06 18:43:01 +01:00
#include "libpq-fe.h"
#include "libpq-int.h"
1998-05-07 16:52:52 +02:00
static bool do_field(const PQprintOpt *po, const PGresult *res,
const int i, const int j, const int fs_len,
1998-05-07 16:52:52 +02:00
char **fields,
const int nFields, const char **fieldNames,
1998-05-07 16:52:52 +02:00
unsigned char *fieldNotNum, int *fieldMax,
const int fieldMaxLen, FILE *fout);
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);
static void output_row(FILE *fout, const PQprintOpt *po, const int nFields, char **fields,
1998-05-07 16:52:52 +02:00
unsigned char *fieldNotNum, int *fieldMax, char *border,
const int row_index);
static void fill(int length, int max, char filler, FILE *fp);
1998-05-07 16:52:52 +02:00
/*
* PQprint()
*
* Format results of a query for printing.
*
* PQprintOpt is a typedef (structure) that contains
1998-05-07 16:52:52 +02:00
* various flags and options. consult libpq-fe.h for
* details
*
* This function should probably be removed sometime since psql
* doesn't use it anymore. It is unclear to what extent this is used
* by external clients, however.
1998-05-07 16:52:52 +02:00
*/
void
PQprint(FILE *fout, const PGresult *res, const PQprintOpt *po)
1998-05-07 16:52:52 +02:00
{
int nFields;
nFields = PQnfields(res);
if (nFields > 0)
{ /* only print rows with at least 1 field. */
int i,
j;
int nTups;
int *fieldMax = NULL; /* in case we don't use them */
unsigned char *fieldNotNum = NULL;
char *border = NULL;
char **fields = NULL;
const char **fieldNames = NULL;
1998-05-07 16:52:52 +02:00
int fieldMaxLen = 0;
int numFieldName;
int fs_len = strlen(po->fieldSep);
int total_line_length = 0;
bool usePipe = false;
1998-05-07 16:52:52 +02:00
char *pagerenv;
2005-10-15 04:49:52 +02:00
#if !defined(WIN32)
sigset_t osigset;
bool sigpipe_masked = false;
bool sigpipe_pending;
#endif
2001-03-22 05:01:46 +01:00
2001-01-06 18:43:01 +01:00
#ifdef TIOCGWINSZ
struct winsize screen_size;
#else
struct winsize
{
int ws_row;
int ws_col;
} screen_size;
#endif
1998-05-07 16:52:52 +02:00
nTups = PQntuples(res);
fieldNames = (const char **) calloc(nFields, sizeof(char *));
fieldNotNum = (unsigned char *) calloc(nFields, 1);
fieldMax = (int *) calloc(nFields, sizeof(int));
if (!fieldNames || !fieldNotNum || !fieldMax)
1998-05-07 16:52:52 +02:00
{
fprintf(stderr, libpq_gettext("out of memory\n"));
goto exit;
1998-05-07 16:52:52 +02:00
}
for (numFieldName = 0;
po->fieldName && po->fieldName[numFieldName];
numFieldName++)
;
for (j = 0; j < nFields; j++)
{
int len;
const char *s = (j < numFieldName && po->fieldName[j][0]) ?
1998-05-07 16:52:52 +02:00
po->fieldName[j] : PQfname(res, j);
fieldNames[j] = s;
len = s ? strlen(s) : 0;
fieldMax[j] = len;
len += fs_len;
if (len > fieldMaxLen)
fieldMaxLen = len;
total_line_length += len;
}
total_line_length += nFields * strlen(po->fieldSep) + 1;
if (fout == NULL)
fout = stdout;
if (po->pager && fout == stdout && isatty(fileno(stdin)) &&
isatty(fileno(stdout)))
1998-05-07 16:52:52 +02:00
{
2001-01-06 18:43:01 +01:00
/*
* If we think there'll be more than one screen of output, try to
* pipe to the pager program.
*/
1998-05-07 16:52:52 +02:00
#ifdef TIOCGWINSZ
if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) == -1 ||
screen_size.ws_col == 0 ||
screen_size.ws_row == 0)
{
screen_size.ws_row = 24;
screen_size.ws_col = 80;
}
2001-01-06 18:43:01 +01:00
#else
screen_size.ws_row = 24;
screen_size.ws_col = 80;
1998-05-07 16:52:52 +02:00
#endif
/*
* Since this function is no longer used by psql, we don't examine
* PSQL_PAGER. It's possible that the hypothetical external users
* of the function would like that to happen, but in the name of
* backwards compatibility, we'll stick to just examining PAGER.
*/
1998-05-07 16:52:52 +02:00
pagerenv = getenv("PAGER");
Handle empty or all-blank PAGER setting more sanely in psql. If the PAGER environment variable is set but contains an empty string, psql would pass it to "sh" which would silently exit, causing whatever query output we were printing to vanish entirely. This is quite mystifying; it took a long time for us to figure out that this was the cause of Joseph Brenner's trouble report. Rather than allowing that to happen, we should treat this as another way to specify "no pager". (We could alternatively treat it as selecting the default pager, but it seems more likely that the former is what the user meant to achieve by setting PAGER this way.) Nonempty, but all-white-space, PAGER values have the same behavior, and it's pretty easy to test for that, so let's handle that case the same way. Most other cases of faulty PAGER values will result in the shell printing some kind of complaint to stderr, which should be enough to diagnose the problem, so we don't need to work harder than this. (Note that there's been an intentional decision not to be very chatty about apparent failure returns from the pager process, since that may happen if, eg, the user quits the pager with control-C or some such. I'd just as soon not start splitting hairs about which exit codes might merit making our own report.) libpq's old PQprint() function was already on board with ignoring empty PAGER values, but for consistency, make it ignore all-white-space values as well. It's been like this a long time, so back-patch to all supported branches. Discussion: https://postgr.es/m/CAFfgvXWLOE2novHzYjmQK8-J6TmHz42G8f3X0SORM44+stUGmw@mail.gmail.com
2016-12-07 18:19:56 +01:00
/* if PAGER is unset, empty or all-white-space, don't use pager */
1998-05-07 16:52:52 +02:00
if (pagerenv != NULL &&
Handle empty or all-blank PAGER setting more sanely in psql. If the PAGER environment variable is set but contains an empty string, psql would pass it to "sh" which would silently exit, causing whatever query output we were printing to vanish entirely. This is quite mystifying; it took a long time for us to figure out that this was the cause of Joseph Brenner's trouble report. Rather than allowing that to happen, we should treat this as another way to specify "no pager". (We could alternatively treat it as selecting the default pager, but it seems more likely that the former is what the user meant to achieve by setting PAGER this way.) Nonempty, but all-white-space, PAGER values have the same behavior, and it's pretty easy to test for that, so let's handle that case the same way. Most other cases of faulty PAGER values will result in the shell printing some kind of complaint to stderr, which should be enough to diagnose the problem, so we don't need to work harder than this. (Note that there's been an intentional decision not to be very chatty about apparent failure returns from the pager process, since that may happen if, eg, the user quits the pager with control-C or some such. I'd just as soon not start splitting hairs about which exit codes might merit making our own report.) libpq's old PQprint() function was already on board with ignoring empty PAGER values, but for consistency, make it ignore all-white-space values as well. It's been like this a long time, so back-patch to all supported branches. Discussion: https://postgr.es/m/CAFfgvXWLOE2novHzYjmQK8-J6TmHz42G8f3X0SORM44+stUGmw@mail.gmail.com
2016-12-07 18:19:56 +01:00
strspn(pagerenv, " \t\r\n") != strlen(pagerenv) &&
1998-05-07 16:52:52 +02:00
!po->html3 &&
((po->expanded &&
nTups * (nFields + 1) >= screen_size.ws_row) ||
(!po->expanded &&
nTups * (total_line_length / screen_size.ws_col + 1) *
(1 + (po->standard != 0)) >= screen_size.ws_row -
1998-05-07 16:52:52 +02:00
(po->header != 0) *
(total_line_length / screen_size.ws_col + 1) * 2
- (po->header != 0) * 2 /* row count and newline */
)))
{
fflush(NULL);
1998-05-07 16:52:52 +02:00
fout = popen(pagerenv, "w");
if (fout)
{
usePipe = true;
#ifndef WIN32
if (pq_block_sigpipe(&osigset, &sigpipe_pending) == 0)
sigpipe_masked = true;
#endif /* WIN32 */
1998-05-07 16:52:52 +02:00
}
else
fout = stdout;
}
}
if (!po->expanded && (po->align || po->html3))
{
fields = (char **) calloc((size_t) nTups + 1,
nFields * sizeof(char *));
if (!fields)
1998-05-07 16:52:52 +02:00
{
fprintf(stderr, libpq_gettext("out of memory\n"));
goto exit;
1998-05-07 16:52:52 +02:00
}
}
else if (po->header && !po->html3)
{
if (po->expanded)
{
if (po->align)
fprintf(fout, libpq_gettext("%-*s%s Value\n"),
fieldMaxLen - fs_len, libpq_gettext("Field"), po->fieldSep);
1998-05-07 16:52:52 +02:00
else
fprintf(fout, libpq_gettext("%s%sValue\n"), libpq_gettext("Field"), po->fieldSep);
1998-05-07 16:52:52 +02:00
}
else
{
int len = 0;
for (j = 0; j < nFields; j++)
{
const char *s = fieldNames[j];
1998-05-07 16:52:52 +02:00
fputs(s, fout);
len += strlen(s) + fs_len;
if ((j + 1) < nFields)
fputs(po->fieldSep, fout);
}
fputc('\n', fout);
for (len -= fs_len; len--; fputc('-', fout));
fputc('\n', fout);
}
}
if (po->expanded && po->html3)
{
if (po->caption)
fprintf(fout, "<center><h2>%s</h2></center>\n", po->caption);
1998-05-07 16:52:52 +02:00
else
fprintf(fout,
"<center><h2>"
1998-05-07 16:52:52 +02:00
"Query retrieved %d rows * %d fields"
"</h2></center>\n",
1998-05-07 16:52:52 +02:00
nTups, nFields);
}
for (i = 0; i < nTups; i++)
{
if (po->expanded)
{
if (po->html3)
fprintf(fout,
"<table %s><caption align=\"top\">%d</caption>\n",
1998-05-07 16:52:52 +02:00
po->tableOpt ? po->tableOpt : "", i);
else
fprintf(fout, libpq_gettext("-- RECORD %d --\n"), i);
1998-05-07 16:52:52 +02:00
}
for (j = 0; j < nFields; j++)
{
if (!do_field(po, res, i, j, fs_len, fields, nFields,
fieldNames, fieldNotNum,
fieldMax, fieldMaxLen, fout))
goto exit;
}
1998-05-07 16:52:52 +02:00
if (po->html3 && po->expanded)
fputs("</table>\n", fout);
}
if (!po->expanded && (po->align || po->html3))
{
if (po->html3)
{
if (po->header)
{
if (po->caption)
fprintf(fout,
"<table %s><caption align=\"top\">%s</caption>\n",
1998-05-07 16:52:52 +02:00
po->tableOpt ? po->tableOpt : "",
po->caption);
else
fprintf(fout,
"<table %s><caption align=\"top\">"
1998-05-07 16:52:52 +02:00
"Retrieved %d rows * %d fields"
"</caption>\n",
po->tableOpt ? po->tableOpt : "", nTups, nFields);
}
else
fprintf(fout, "<table %s>", po->tableOpt ? po->tableOpt : "");
}
if (po->header)
border = do_header(fout, po, nFields, fieldMax, fieldNames,
fieldNotNum, fs_len, res);
for (i = 0; i < nTups; i++)
output_row(fout, po, nFields, fields,
fieldNotNum, fieldMax, border, i);
}
if (po->header && !po->html3)
fprintf(fout, "(%d row%s)\n\n", PQntuples(res),
(PQntuples(res) == 1) ? "" : "s");
if (po->html3 && !po->expanded)
fputs("</table>\n", fout);
exit:
free(fieldMax);
free(fieldNotNum);
free(border);
if (fields)
{
/* if calloc succeeded, this shouldn't overflow size_t */
size_t numfields = ((size_t) nTups + 1) * (size_t) nFields;
while (numfields-- > 0)
free(fields[numfields]);
free(fields);
}
free(fieldNames);
1998-05-07 16:52:52 +02:00
if (usePipe)
{
#ifdef WIN32
_pclose(fout);
#else
1998-05-07 16:52:52 +02:00
pclose(fout);
2005-10-15 04:49:52 +02:00
/* we can't easily verify if EPIPE occurred, so say it did */
if (sigpipe_masked)
pq_reset_sigpipe(&osigset, sigpipe_pending, true);
#endif /* WIN32 */
1998-05-07 16:52:52 +02:00
}
}
}
static bool
do_field(const PQprintOpt *po, const PGresult *res,
const int i, const int j, const int fs_len,
1998-05-07 16:52:52 +02:00
char **fields,
const int nFields, char const **fieldNames,
1998-05-07 16:52:52 +02:00
unsigned char *fieldNotNum, int *fieldMax,
const int fieldMaxLen, FILE *fout)
{
const char *pval,
*p;
1998-05-07 16:52:52 +02:00
int plen;
bool skipit;
plen = PQgetlength(res, i, j);
pval = PQgetvalue(res, i, j);
if (plen < 1 || !pval || !*pval)
{
if (po->align || po->expanded)
skipit = true;
else
{
skipit = false;
goto efield;
}
}
else
skipit = false;
if (!skipit)
{
if (po->align && !fieldNotNum[j])
{
/* Detect whether field contains non-numeric data */
char ch = '0';
1999-05-25 18:15:34 +02:00
Fix incautious handling of possibly-miscoded strings in client code. An incorrectly-encoded multibyte character near the end of a string could cause various processing loops to run past the string's terminating NUL, with results ranging from no detectable issue to a program crash, depending on what happens to be in the following memory. This isn't an issue in the server, because we take care to verify the encoding of strings before doing any interesting processing on them. However, that lack of care leaked into client-side code which shouldn't assume that anyone has validated the encoding of its input. Although this is certainly a bug worth fixing, the PG security team elected not to regard it as a security issue, primarily because any untrusted text should be sanitized by PQescapeLiteral or the like before being incorporated into a SQL or psql command. (If an app fails to do so, the same technique can be used to cause SQL injection, with probably much more dire consequences than a mere client-program crash.) Those functions were already made proof against this class of problem, cf CVE-2006-2313. To fix, invent PQmblenBounded() which is like PQmblen() except it won't return more than the number of bytes remaining in the string. In HEAD we can make this a new libpq function, as PQmblen() is. It seems imprudent to change libpq's API in stable branches though, so in the back branches define PQmblenBounded as a macro in the files that need it. (Note that just changing PQmblen's behavior would not be a good idea; notably, it would completely break the escaping functions' defense against this exact problem. So we just want a version for those callers that don't have any better way of handling this issue.) Per private report from houjingyi. Back-patch to all supported branches.
2021-06-07 20:15:25 +02:00
for (p = pval; *p; p += PQmblenBounded(p, res->client_encoding))
{
ch = *p;
if (!((ch >= '0' && ch <= '9') ||
ch == '.' ||
ch == 'E' ||
ch == 'e' ||
ch == ' ' ||
ch == '-'))
{
fieldNotNum[j] = 1;
break;
}
}
/*
* Above loop will believe E in first column is numeric; also, we
* insist on a digit in the last column for a numeric. This test
* is still not bulletproof but it handles most cases.
*/
if (*pval == 'E' || *pval == 'e' ||
!(ch >= '0' && ch <= '9'))
1998-05-07 16:52:52 +02:00
fieldNotNum[j] = 1;
}
1998-05-07 16:52:52 +02:00
if (!po->expanded && (po->align || po->html3))
{
if (plen > fieldMax[j])
fieldMax[j] = plen;
if (!(fields[i * nFields + j] = (char *) malloc(plen + 1)))
1998-05-07 16:52:52 +02:00
{
fprintf(stderr, libpq_gettext("out of memory\n"));
return false;
1998-05-07 16:52:52 +02:00
}
strcpy(fields[i * nFields + j], pval);
1998-05-07 16:52:52 +02:00
}
else
{
if (po->expanded)
{
if (po->html3)
fprintf(fout,
"<tr><td align=\"left\"><b>%s</b></td>"
"<td align=\"%s\">%s</td></tr>\n",
1998-05-07 16:52:52 +02:00
fieldNames[j],
fieldNotNum[j] ? "left" : "right",
pval);
1998-05-07 16:52:52 +02:00
else
{
if (po->align)
fprintf(fout,
"%-*s%s %s\n",
fieldMaxLen - fs_len, fieldNames[j],
po->fieldSep,
pval);
1998-05-07 16:52:52 +02:00
else
fprintf(fout,
"%s%s%s\n",
fieldNames[j], po->fieldSep, pval);
1998-05-07 16:52:52 +02:00
}
}
else
{
if (!po->html3)
{
fputs(pval, fout);
1998-05-07 16:52:52 +02:00
efield:
if ((j + 1) < nFields)
fputs(po->fieldSep, fout);
else
fputc('\n', fout);
}
}
}
}
return true;
1998-05-07 16:52:52 +02:00
}
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)
1998-05-07 16:52:52 +02:00
{
int j; /* for loop index */
char *border = NULL;
if (po->html3)
fputs("<tr>", fout);
else
{
int tot = 0;
int n = 0;
char *p = NULL;
for (; n < nFields; n++)
tot += fieldMax[n] + fs_len + (po->standard ? 2 : 0);
if (po->standard)
tot += fs_len * 2 + 2;
border = malloc(tot + 1);
if (!border)
{
fprintf(stderr, libpq_gettext("out of memory\n"));
return NULL;
1998-05-07 16:52:52 +02:00
}
p = border;
if (po->standard)
{
char *fs = po->fieldSep;
1998-05-07 16:52:52 +02:00
while (*fs++)
*p++ = '+';
}
for (j = 0; j < nFields; j++)
{
int len;
for (len = fieldMax[j] + (po->standard ? 2 : 0); len--; *p++ = '-');
if (po->standard || (j + 1) < nFields)
{
char *fs = po->fieldSep;
1998-05-07 16:52:52 +02:00
while (*fs++)
*p++ = '+';
}
}
*p = '\0';
if (po->standard)
fprintf(fout, "%s\n", border);
}
if (po->standard)
fputs(po->fieldSep, fout);
for (j = 0; j < nFields; j++)
{
const char *s = PQfname(res, j);
1998-05-07 16:52:52 +02:00
if (po->html3)
{
fprintf(fout, "<th align=\"%s\">%s</th>",
1998-05-07 16:52:52 +02:00
fieldNotNum[j] ? "left" : "right", fieldNames[j]);
}
else
{
int n = strlen(s);
if (n > fieldMax[j])
fieldMax[j] = n;
if (po->standard)
fprintf(fout,
fieldNotNum[j] ? " %-*s " : " %*s ",
fieldMax[j], s);
else
fprintf(fout, fieldNotNum[j] ? "%-*s" : "%*s", fieldMax[j], s);
if (po->standard || (j + 1) < nFields)
fputs(po->fieldSep, fout);
}
}
if (po->html3)
fputs("</tr>\n", fout);
else
fprintf(fout, "\n%s\n", border);
return border;
}
static void
output_row(FILE *fout, const PQprintOpt *po, const int nFields, char **fields,
1998-05-07 16:52:52 +02:00
unsigned char *fieldNotNum, int *fieldMax, char *border,
const int row_index)
{
int field_index; /* for loop index */
if (po->html3)
fputs("<tr>", fout);
else if (po->standard)
fputs(po->fieldSep, fout);
for (field_index = 0; field_index < nFields; field_index++)
{
char *p = fields[row_index * nFields + field_index];
if (po->html3)
fprintf(fout, "<td align=\"%s\">%s</td>",
1998-05-07 16:52:52 +02:00
fieldNotNum[field_index] ? "left" : "right", p ? p : "");
else
{
fprintf(fout,
fieldNotNum[field_index] ?
(po->standard ? " %-*s " : "%-*s") :
(po->standard ? " %*s " : "%*s"),
fieldMax[field_index],
p ? p : "");
if (po->standard || field_index + 1 < nFields)
fputs(po->fieldSep, fout);
}
}
if (po->html3)
fputs("</tr>", fout);
else if (po->standard)
fprintf(fout, "\n%s", border);
fputc('\n', fout);
}
/*
* really old printing routines
*/
void
PQdisplayTuples(const PGresult *res,
FILE *fp, /* where to send the output */
int fillAlign, /* pad the fields with spaces */
const char *fieldSep, /* field separator */
int printHeader, /* display headers? */
int quiet
)
{
#define DEFAULT_FIELD_SEP " "
int i,
j;
int nFields;
int nTuples;
int *fLength = NULL;
if (fieldSep == NULL)
fieldSep = DEFAULT_FIELD_SEP;
/* Get some useful info about the results */
nFields = PQnfields(res);
nTuples = PQntuples(res);
if (fp == NULL)
fp = stdout;
/* Figure the field lengths to align to */
/* will be somewhat time consuming for very large results */
if (fillAlign)
{
fLength = (int *) malloc(nFields * sizeof(int));
if (!fLength)
{
fprintf(stderr, libpq_gettext("out of memory\n"));
return;
}
for (j = 0; j < nFields; j++)
{
fLength[j] = strlen(PQfname(res, j));
for (i = 0; i < nTuples; i++)
{
int flen = PQgetlength(res, i, j);
if (flen > fLength[j])
fLength[j] = flen;
}
}
}
if (printHeader)
{
/* first, print out the attribute names */
for (i = 0; i < nFields; i++)
{
fputs(PQfname(res, i), fp);
if (fillAlign)
fill(strlen(PQfname(res, i)), fLength[i], ' ', fp);
fputs(fieldSep, fp);
}
fprintf(fp, "\n");
/* Underline the attribute names */
for (i = 0; i < nFields; i++)
{
if (fillAlign)
fill(0, fLength[i], '-', fp);
fputs(fieldSep, fp);
}
fprintf(fp, "\n");
}
/* next, print out the instances */
for (i = 0; i < nTuples; i++)
{
for (j = 0; j < nFields; j++)
{
fprintf(fp, "%s", PQgetvalue(res, i, j));
if (fillAlign)
fill(strlen(PQgetvalue(res, i, j)), fLength[j], ' ', fp);
fputs(fieldSep, fp);
}
fprintf(fp, "\n");
}
if (!quiet)
fprintf(fp, "\nQuery returned %d row%s.\n", PQntuples(res),
(PQntuples(res) == 1) ? "" : "s");
fflush(fp);
free(fLength);
}
void
PQprintTuples(const PGresult *res,
FILE *fout, /* output stream */
int PrintAttNames, /* print attribute names or not */
int TerseOutput, /* delimiter bars or not? */
int colWidth /* width of column, if 0, use variable width */
)
1998-05-07 16:52:52 +02:00
{
int nFields;
int nTups;
int i,
j;
char formatString[80];
char *tborder = NULL;
nFields = PQnfields(res);
nTups = PQntuples(res);
if (colWidth > 0)
sprintf(formatString, "%%s %%-%ds", colWidth);
else
sprintf(formatString, "%%s %%s");
if (nFields > 0)
{ /* only print rows with at least 1 field. */
if (!TerseOutput)
{
int width;
width = nFields * 14;
tborder = (char *) malloc(width + 1);
if (!tborder)
{
fprintf(stderr, libpq_gettext("out of memory\n"));
return;
}
for (i = 0; i < width; i++)
tborder[i] = '-';
tborder[width] = '\0';
fprintf(fout, "%s\n", tborder);
}
for (i = 0; i < nFields; i++)
{
if (PrintAttNames)
{
fprintf(fout, formatString,
TerseOutput ? "" : "|",
PQfname(res, i));
}
}
if (PrintAttNames)
{
if (TerseOutput)
fprintf(fout, "\n");
else
fprintf(fout, "|\n%s\n", tborder);
}
for (i = 0; i < nTups; i++)
{
for (j = 0; j < nFields; j++)
{
const char *pval = PQgetvalue(res, i, j);
1998-05-07 16:52:52 +02:00
fprintf(fout, formatString,
TerseOutput ? "" : "|",
pval ? pval : "");
}
if (TerseOutput)
fprintf(fout, "\n");
else
fprintf(fout, "|\n%s\n", tborder);
}
}
free(tborder);
}
/* simply send out max-length number of filler characters to fp */
static void
fill(int length, int max, char filler, FILE *fp)
{
int count;
count = max - length;
while (count-- >= 0)
putc(filler, fp);
}